Thinkphp6源碼分析之解析,Thinkphp6路由,Thinkphp6路由源碼解析,Thinkphp6請求流程解析,Thinkphp6源碼


Thinkphp6源碼解析之分析 路由篇-請求流程

0x00 前言:

第一次寫這么長的博客,所以可能排版啊,分析啊,什么的可能會比較亂。但是我大致的流程已經覺得是說的夠清楚了。幾乎是每行源碼上都有注釋。關於請求流程大概是:

  1. 入口文件先實例化容器,然后再通過容器去獲取到Http對象 (Web管理類),然后執行Http對象中的run方法。
  2. 方法內會創建一個Request對象,然后將對象綁定到容器內。然后再到runWithRequest方法,執行應用程序
  3. runWithRequest方法內會初始化當前應用,簡單來說就比如加載一下語言包,加載一下應用文件。common.php公共函數文件。helper.php助手函數文件、.env環境變量、運行開始的時間、設置時區、加載中間件等等。然后到dispatchToRoute方法,傳入當前的Request請求對象。
  4. dispatchToRoute方法,這里我姑且稱為路由初始化方法。這里主要就是檢測配置文件,是否開啟了路由。如果開啟了路由。就加載路由文件。並且設置一個匿名函數。只有在調用的時候才會加載設置的路由。接着會通過容器獲取route類的實例。並且傳入當前Request對象和路由配置的匿名函數並執行里面的dispatch方法
  5. dispatch方法,主要就是路由初始化。判斷路由是否配置。如果沒有配置就直接執行默認控制器和默認方法。如果有的話。就加載一下路由配置,再執行check方法,通過check方法。去檢測是什么路由。然后調用url方法傳入當前的url地址
  6. url方法執行並且會返回一個Url基礎類
  7. 這里我稱之為url且切割類吧。他主要的作用就是將傳入的Request對象,rule路由規則對象,以及當前的url地址。把url地址解析。這里又去調用了一個parseUrl方法。
  8. 這個方法我就不多介紹了。它主要的作用就是分割出來要執行的control和function。
  9. 到這里就結束了url的部分。又回到了第五部。去調用url類中的init方法(路由后置操作。中間件).然后通過middleware中的then。到最后執行一個上一步返回的Url類中的run方法
  10. run這里主要就是最通過exec再去獲取控制器(controller)和對應的方法(function)的結果。然后創建一個Response對象。最后返回
  11. 最后回到了入口文件run方法下面還有一個send方法。源碼就不貼了。他的作用就是輸出
  12. 入口文件最后一行。調用了一下Http類的end方法。簡單說就是掛個HttpEnd中間件。然后執行中間件。最后再記錄一下日志。

這里打個小廣告:php交流群:159789818。歡迎想學php以及正在學習php的朋友加入一起討論學習php,thinkphp框架,laravel框架,yii框架,web前端技術,支付平台對接,微信公眾號,微信小程序等微信開發,cms二次開發,全棧工程師技術交流,共同學習進步!

0x01 源碼解析

1、public/index.php

// [ 應用入口文件 ]
//定義根命名空間
namespace think;
//引入composer
require __DIR__ . '/../vendor/autoload.php';
//通過Ioc容器將HTTP類實例出來
// 執行HTTP應用並響應
$http = (new App())->http;
//執行HTTP類中的run類方法 並返回一個response對象
$response = $http->run();
//執行response對象中的send類方法  該方法是處理並輸出http狀態碼以及頁面內容
$response->send();
//執行response對象中的send方法
$http->end($response);

2、通過\Think\App容器獲取到Http對象,然后再執行Http對象中的run方法

/**
 * 執行應用程序
 * @access public
 * @param Request|null $request
 * @return Response
 */
public function run(Request $request = null): Response
{
    //判斷是否傳入Request對象,如果沒有則創建
    $request = $request ?? $this->app->make('request', [], true);
    //將Request綁定到App容器內
    $this->app->instance('request', $request);
    
    try {
    //runWithRequest方法 作用是執行應用程序
        $response = $this->runWithRequest($request);
    } catch (Throwable $e) {
        //如果捕捉到Throwable異常 則執行reportException方法
        //調用Handle::class實例中的report方法。收集異常信息
        $this->reportException($e);
        //通過調用Handle::class實例中的render方法
        //獲取異常信息輸出流
        $response = $this->renderException($request, $e);
    }
    //return 內容
    return $response;
}

3、\Think\Http->runWithRequest() 初始化應用程序,並執行

protected function runWithRequest(Request $request)
{   
    //初始化應用程序
    $this->initialize();
    // 加載全局中間件
    $this->loadMiddleware();
    // 設置開啟事件機制
    $this->app->event->withEvent($this->app->config->get('app.with_event', true));
    // 監聽HttpRun
    $this->app->event->trigger(HttpRun::class);
    //這里重點
    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            //通過dispatchToRoute方法加載路由
            return $this->dispatchToRoute($request);
        });
}

4、\Think\Http->dispatchToRoute()  

protected function dispatchToRoute($request)
{
    //通過容器控制反轉快速獲取config類實例
    //並獲取配置文件中的with_route 判斷是否加載路由
    //如果加載則返回一個匿名函數。里面是路由文件內的設置
    $withRoute = $this->app->config->get('app.with_route', true) ? function () {
        $this->loadRoutes();
    } : null;
    //執行\Think\Route類中的dispatch方法,並且將獲取到的路由
    //文件以及當前的Request實例傳入到路由中,然后進行路由調度
    return $this->app->route->dispatch($request, $withRoute);
}

5、\Think\Route->dispatch()  路由

public function dispatch(Request $request, $withRoute = null)
{
    //設置傳入的Request對象到當前對象的屬性上
    $this->request = $request;
    //同上 這個是設置host
    $this->host    = $this->request->host(true);
    //執行Route init 方法 初始化
    $this->init();
    //判斷的withRoute是否為真
    if ($withRoute) {
        //執行傳入過來的匿名函數,加載路由
        $withRoute();
        //官方注釋的是檢測url路由,我這里姑且認為是路由分發吧
        //check返回的是think\route\Dispatch 路由調度基礎類對象
        $dispatch = $this->check();
    } else {
        //調用think\route\dispatch\Url類
        //將當前的url地址傳入進去,進行默認的url解析
        $dispatch = $this->url($this->path());
    }
    //執行Dispatch對象中的init方法
    //這里用於綁定控制器和方法以及路由后置操作,例如:中間件、綁定模型數據
    $dispatch->init($this->app);
    //執行路由調度。並返回一個Response對象
    return $this->app->middleware->pipeline('route')
        ->send($request)
        ->then(function () use ($dispatch) {
            return $dispatch->run();
        });
}

6、\Think\Route->init()  初始化

protected function init()
{
    //合並默認配置以及讀取到的route.php配置
    $this->config = array_merge($this->config, $this->app->config->get('route'));
    //判斷路由中間件是否存儲,如果存在則調用middleware類中的import方法
    //注冊route中間件
    if (!empty($this->config['middleware'])) {
        $this->app->middleware->import($this->config['middleware'], 'route');
    }
    //是否延遲解析
    $this->lazy($this->config['url_lazy_route']);
    //讀取到的 是否合並路由配置項賦值到類變量mergeRuleRegex中
    $this->mergeRuleRegex = $this->config['route_rule_merge'];
    //獲取配置:是否刪除url最后的斜線
    $this->removeSlash    = $this->config['remove_slash'];
    //是否去除url最后的斜線
    $this->group->removeSlash($this->removeSlash);
}

7、Think\Route\Dispatch->init()

public function init(App $app)
{
    $this->app = $app;
    // 執行路由后置操作
    $this->doRouteAfter();
}

8、Think\Route\Dispatch->run()

public function run(): Response
{
    //判斷$this->rule路由規則是否為RuleItem類的實例
    //判斷當前請求方法,是不是OPTIONS以及
    //判斷當前路由規則是否為自動注冊的OPTIONS路由
    if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) {
        //獲取當前的路由列表
        $rules = $this->rule->getRouter()->getRule($this->rule->getRule());
        $allow = [];
        foreach ($rules as $item) {
            //這里是循環把所有路由全部轉成大寫
            $allow[] = strtoupper($item->getMethod());
        }
        //創建並返回一個Response對象,調用create靜態方法
        return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]);
    }
    //如果上面的不匹配則調用當前Dispatch類中的exec方法
    //實例化控制器以及方法
    $data = $this->exec();
    //最后動態的返回一個Response對象。xml、json等等
    return $this->autoResponse($data);
}

9、\Think\Route->check() 

 

public function check(): Dispatch
{
    //轉換PATH_INFO分隔符,拼接url
    $url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
    //獲取是否完全匹配 配置項
    $completeMatch = $this->config['route_complete_match'];
    //調用checkDomain檢測是否為域名路由如果不是則返回false
    //如果是域名路由,則返回一個Domain對象。並且執行對象中的check方法
    //並把當前的Request請求對象以及url地址和是否完全匹配路由項傳入進去
    $result = $this->checkDomain()->check($this->request, $url, $completeMatch);
    //判斷result是否為false 也就是不是域名路由
    //再判斷是否為跨域路由
    if (false === $result && !empty($this->cross)) {
        // 如果是跨域路由,就將當前的Request請求對象以及url地址和是否完全匹配路由項傳入進去
        $result = $this->cross->check($this->request, $url, $completeMatch);
    }
    //如果是域名路由
    if (false !== $result) {
        //直接返回 $result變量 變量內存儲着 RuleGroup對象實例
        //路由規則
        return $result;
    } elseif ($this->config['url_route_must']) {
        //判斷是否啟用了強制路由,如果啟用了強制路由
        //然后域名路由也匹配不上。就觸發一個路由
        //找不到的異常類
        throw new RouteNotFoundException();
    }
    //以上都不匹配,則調用url函數,傳入當前的url地址
    //返回一個Url類實例
    return $this->url($url);
}

10、\Think\Route->url()

public function url(string $url): UrlDispatch
{
    return new UrlDispatch($this->request, $this->group, $url);
}

11、\Think\Route\dispatch\Url  url切割類(自己給的稱呼)

//構造函數
public function __construct(Request $request, Rule $rule, $dispatch, array $param = [], int $code = null)
{
    //獲取傳入來的Request對象,存儲到類成員變量內
    $this->request = $request;
    //獲取到路由列表,也放到類成員變量內
    $this->rule    = $rule;
    // 調用類中的parseUrl方法 解析URL規則
    $dispatch = $this->parseUrl($dispatch);
    //調用父類構造函數
    parent::__construct($request, $rule, $dispatch, $this->param, $code);
}

protected function parseUrl(string $url): array
{
    //獲取到分隔符
    $depr = $this->rule->config('pathinfo_depr');
    //獲取當前域名
    $bind = $this->rule->getRouter()->getDomainBind();
    //如果域名不為空,並且正則匹配的到
    if ($bind && preg_match('/^[a-z]/is', $bind)) {
        //切割url,換成配置項中的PATH_INFO分隔符
        $bind = str_replace('/', $depr, $bind);
        // 如果有域名綁定
        $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
    }
    //調用rule類中的parseUrlPath方法,切割pathinfo參數
    //如果url中有參數 返回一個demo吧  ['Index','Demo']
    //第一個為控制器、第二個為方法
    $path = $this->rule->parseUrlPath($url);
    //如果切割的pathinfo為空,則直接返回一個[null,null] 這樣的一個空數組
    if (empty($path)) {
        return [null, null];
    }

    //獲取到第一個下標  控制器
    $controller = !empty($path) ? array_shift($path) : null;
    //正則匹配,如果匹配不到。就彈出一個HttpException異常
    if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) {
        throw new HttpException(404, 'controller not exists:' . $controller);
    }
    //獲取到第二個下標  方法 function
    // 解析操作
    $action = !empty($path) ? array_shift($path) : null;
    $var    = [];

    // 解析額外參數
    //類似於  /index.php/Index/Users/Pascc
    //這樣就會返回一個 三個下標的數組
    if ($path) {
        //這里將多余的下標,放到var變量內
        preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
            $var[$match[1]] = strip_tags($match[2]);
        }, implode('|', $path));
    }
    //獲取到泛域名 再判斷其中是否有*符號
    $panDomain = $this->request->panDomain();
    if ($panDomain && $key = array_search('*', $var)) {
        // 泛域名賦值
        $var[$key] = $panDomain;
    }

    // 設置當前請求的參數
    $this->param = $var;

    // 封裝路由
    $route = [$controller, $action];
    //判斷路由,是否存在 不存在則彈出未找到路由
    if ($this->hasDefinedRoute($route)) {
        throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
    }
    //返回路由
    return $route;

12、\Think\Route\dispatch\controller->init()  綁定控制器和方法

public function init(App $app)
{
    parent::init($app);
    
    $result = $this->dispatch;

    if (is_string($result)) {
        $result = explode('/', $result);
    }

    // 獲取控制器名
    $controller = strip_tags($result[0] ?: $this->rule->config('default_controller'));

    if (strpos($controller, '.')) {
        $pos              = strrpos($controller, '.');
        $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1));
    } else {
        $this->controller = Str::studly($controller);
    }

    // 獲取操作名
    $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action'));

    // 設置當前請求的控制器、操作
    $this->request
        ->setController($this->controller)
        ->setAction($this->actionName);
}

13、接下來流程就是回到第5步

最終會返回一個Response對象。流程又回到了第2步過程

14、然后會在入口文件中

namespace think;
//引入composer
require __DIR__ . '/../vendor/autoload.php';
$http = (new App())->http;
$response = $http->run();
//執行返回的Response對象中的send方法
//執行response對象中的send類方法  該方法是處理並輸出http狀態碼以及頁面內容
$response->send();
//執行Http對象中的send方法 
$http->end($response);
//最終輸出到頁面上我

0x02 Demo

如果覺得本篇文章對您有所幫助,不妨點一下贊再走。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM