PHP做爲世界上最好的編程語音,被普遍的運用到Web開發中。由於其語法和C相似,有着很是平緩的學習曲線,愈來愈多的人使用PHP進行Web產品的快速開發。PHP世界裏也涌現了不少開發框架,好比Laravel、ThinkPHP等,但不論何總框架,他們在處理Web請求時的模式都是同樣的,本文首先闡述PHP開發Web應用的基本架構,而後分別分析Laravel和ThinkPHP在處理Web請求時的處理流程。php
PHP開發Web應用時因此的請求須要指向具體的入口文件。WebServer是一個內容分發者,他接受用戶的請求後,若是是請求的是css、js等靜態文件,WebServer會找到這個文件,而後發送給瀏覽器;若是請求的是/index.php,根據配置文件,WebServer知道這個不是靜態文件,須要去找PHP解析器來處理,那麼他會把這個請求簡單處理後交給PHP解析器。
css
WebServer會依據CGI協議,將請求的Url、數據、Http Header等信息發送給PHP解析器,接下來PHP解析器會解析php.ini文件,初始化執行環境,而後處理請求,再以CGI規定的格式返回處理後的結果,退出進程。web server再把結果返回給瀏覽器。整個處理過程如上圖所示。laravel
這裏的PHP解析器就是實現了CGI協議的程序,每次請求到來時他會解析php.ini文件,初始化執行環境,這就致使PHP解析器性能低下,因而就出現了CGI的改良升級版FastCGI。FastCGI是一種語言無關的協議,用來溝通程序(如PHP, Python, Java)和Web服務器(Apache2, Nginx), 理論上任何語言編寫的程序均可以經過FastCGI來提供Web服務。它的特色是會在動態分配處理進程給請求,以達到提升效率的目的,大多數FastCGI實現都會維護一個進程池。FastCGI會先啓一個master進程,解析配置文件,初始化執行環境,而後再啓動多個worker進程。當請求過來時,master進程會這個請求傳遞給一個worker進程,而後當即接受下一個請求。並且當worker進程不夠用時,master能夠根據配置預先啓動幾個worker進程等待;固然空閒worker進程太多時,也會自動關閉,這樣就提升了性能,節約了系統資源。整個過程FastCGI扮演着對CGI進程進行管理的角色。web
PHP-FPM是一個專門針對PHP實現了FastCGI協議的程序,它實際上就是一個PHP FastCGI進程管理器,負責管理一個進程池,調用PHP解析器來處理來自Web服務器的請求。PHP-FPM可以對php.ini文件的修改進行平滑過分。ajax
新建一個helloworld.php文件,寫入下列代碼數據庫
<?php echo "helloworld,"; echo "this is my first php script."; echo phpinfo(); ?>
配置好WebServer和PHP-FPM等php運行環境後,在瀏覽器中訪問該文件就能夠直接獲得輸出。編程
基於某模式將PHP開發經常使用功能封裝實現使開發者快速開發的工具bootstrap
代碼重用:定義包、類、函數的放置和加載規則,建議直接整合Composer及其AutoLoad特性。瀏覽器
請求的分發管理:這個就是路由,Rest風的框架喜歡Rewrite,簡單的一點的框架主要經過參數來定位模塊和方法所在。服務器
配置文件管理:加載和動態加載配置數據
錯誤和異常管理:異常捕捉、錯誤日誌記錄以及錯誤碼規範。
Layout和模板引擎:如何規劃頁面佈局、widget如何重用、ajax頁面如何結合、過時- session如何重定向;數據和模板怎麼渲染成HTML,是否壓縮和設置過時頭。
數據庫:如何融入控制器;支持什麼樣的driver;考慮主從分離的擴展性;以及是否使用ORM
ThinkPHP3.2框架處理流程分析
TP的設計邏輯就是簡單粗暴,面對問題解決問題,因此他的處理流程是基於面向過程的思想,而沒有采用面向對象的依賴注入、控制反轉等思路。他的自動加載、錯誤處理經過php原生函數的回調來實現。TP處理每次請求要通過四個步驟以下圖所示:
index.php是TP的入口文件,全部的請求都由該文件接管,它的工做也很簡單主要是引入ThinkPHP入口文件
<?php // 應用入口文件 // 檢測PHP環境 if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); // 開啓調試模式 建議開發階段開啓 部署階段註釋或者設爲false define('APP_DEBUG',False); // 定義應用目錄 define('APP_PATH','./Application/'); // 引入ThinkPHP入口文件 require './ThinkPHP/ThinkPHP.php';
在ThinkPHP.php中主要記錄初始運行時間和內存開銷,而後完成系統常量判斷及定義,最後載入框架引導類(ThinkThink)並執行Think::start方法進行應用初始化。
應用初始化首先設置錯誤處理機制和自動加載機制
static public function start() { // 註冊AUTOLOAD方法 spl_autoload_register('Think\Think::autoload'); // 設定錯誤和異常處理 register_shutdown_function('Think\Think::fatalError'); set_error_handler('Think\Think::appError'); set_exception_handler('Think\Think::appException');
而後加載相關配置文件和運行模式定義文件,最後調用ThinkApp類的run方法啓動應用
此後TP進入請求處理管道,TP爲管道中定義了14個事件,每一個事件均可以綁定回調函數,請求到達管道後依次觸發這些事件,事件觸發後就會調用綁定到事件的回調函數,整個管道的生命週期由app_init開始,由app_end結束。具體實現上,TP將這些事件命名爲標籤(位),也能夠稱之爲鉤子,將回調函數命名爲行爲,當應用程序運行到標籤的時候,就會被攔截下來,統一執行相關的行爲。
Laravel框架處理流程分析
Laravel框架使用了統一入口,入口文件:/public/index.php
<?php //自動加載文件設置 require __DIR__.'/../bootstrap/autoload.php'; //初始化服務容器(能夠查看一下關於‘服務容器’的相關文檔) $app = require_once __DIR__.'/../bootstrap/app.php'; //經過服務容器生成一個kernel類的實例(Illuminate\Contracts\Http\Kernel實際上只是一個接口,真正生成的實例是App\Http\Kernel類,至於怎麼把接口和類關聯起來,請查看Contracts相關文檔) $kernel = $app->make('Illuminate\Contracts\Http\Kernel'); //運行Kernel類的handle方法,主要動做是運行middleware和啓動URL相關的Contrller $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); //控制器返回結果以後的操做,暫時還沒看,之後補上 $response->send(); $kernel->terminate($request, $response);
laravel的自動加載,其實也就是Composer的自動加載
Composer根據聲明的依賴關係,從相關庫的源下載代碼文件,並根據依賴關係在 Composer 目錄下生成供類自動加載的 PHP 腳本,使用的時候,項目開始處引入 「/vendor/autoload.php」 文件,就能夠直接實例化這些第三方類庫中的類了。
服務容器,也叫IoC容器,其實包含了依賴注入(DI)和控制反轉(IoC)兩部分,是Laravel的真正核心。其餘的各類功能模塊好比 Route(路由)、Eloquent ORM(數據庫 ORM 組件)、Request and Response(請求和響應)等等等等,實際上都是與核心無關的類模塊提供的,這些類從註冊到實例化,最終被使用,其實都是 Laravel 的服務容器負責的。
Kernel實例調用handle方法,意味着Laravel的核心和公用代碼已經準備完畢,此項目正式開始運行
代碼清單/app/Http/Kernel.php
<?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { //這是在調用路由以前須要啓動的中間件,通常都是核心文件,不要修改 protected $middleware = [ 'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode', 'Illuminate\Cookie\Middleware\EncryptCookies', 'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse', 'Illuminate\Session\Middleware\StartSession', 'Illuminate\View\Middleware\ShareErrorsFromSession', 'App\Http\Middleware\VerifyCsrfToken', ]; //這是咱們在router.php文件裏面或者Controller文件裏面,可使用的Middleware元素,能夠自定義加入不少 protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'test' => 'App\Http\Middleware\testMiddleWare', ]; }
能夠看到,其實這個文件裏面沒有handle方法,只有一些屬性定義,因此真正的handle方法,實在父類裏面實現的
代碼清單…/Illuminate/Foundation/Http/Kernel.php
//這個很重要,是項目的一些啓動引導項,Kernel的重要步驟中,首先就是啓動這些文件的bootstrap方法 protected $bootstrappers = [ //檢測環境變量文件是否正常 'Illuminate\Foundation\Bootstrap\DetectEnvironment', //取得配置文件,即把/config/下的全部配置文件讀取到容器(app()->make('config')能夠查看全部配置信息) 'Illuminate\Foundation\Bootstrap\LoadConfiguration', //綁定一個名字爲log的實例到容器,怎麼訪問??(app()->make('log')) 'Illuminate\Foundation\Bootstrap\ConfigureLogging', //設置異常抓取信息,這個還沒仔細看,但大概就是這個意思 'Illuminate\Foundation\Bootstrap\HandleExceptions', //把/config/app.php裏面的aliases項利用PHP庫函數class_alias建立別名,今後,咱們可使用App::make('app')方式取得實例 'Illuminate\Foundation\Bootstrap\RegisterFacades', //把/config/app.php裏面的providers項,註冊到容器 'Illuminate\Foundation\Bootstrap\RegisterProviders', //運行容器中註冊的全部的ServiceProvider中得boot方法 'Illuminate\Foundation\Bootstrap\BootProviders', ]; //真正的handle方法 public function handle($request) { try { //主要是這行,調度了須要運行的方法 return $this->sendRequestThroughRouter($request); } catch (Exception $e) { $this->reportException($e); return $this->renderException($request, $e); } } protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); //運行上述$bootstrappers裏面包含的文件的bootstrap方法,運行的做用,上面已經註釋 $this->bootstrap(); //這是在對URL進行調度以前,也就是運行Route以前,進行的一些準備工做 return (new Pipeline($this->app)) ->send($request) //須要運行$this->middleware裏包含的中間件 ->through($this->middleware) //運行完上述中間件以後,調度dispatchToRouter方法,進行Route的操做 ->then($this->dispatchToRouter()); } //前奏執行完畢以後,進行Route操做 protected function dispatchToRouter() { return function($request) { $this->app->instance('request', $request); //跳轉到Router類的dispatch方法 return $this->router->dispatch($request); }; }
下面就須要根據URL和/app/Http/routes.php文件,進行Route操做
文件清單…/Illuminate/Routing/Router.php
public function dispatch(Request $request) { $this->currentRequest = $request; //在4.2版本里面,Route有一個篩選屬性;5.0以後的版本,被Middleware代替 $response = $this->callFilter('before', $request); if (is_null($response)) { //繼續調度 $response = $this->dispatchToRoute($request); } $response = $this->prepareResponse($request, $response); //在4.2版本里面,Route有一個篩選屬性;5.0以後的版本,被Middleware代替 $this->callFilter('after', $request, $response); return $response; } public function dispatchToRoute(Request $request) { $route = $this->findRoute($request); $request->setRouteResolver(function() use ($route) { return $route; }); $this->events->fire('router.matched', [$route, $request]); $response = $this->callRouteBefore($route, $request); if (is_null($response)) { // 只看這一行,仍是調度文件 $response = $this->runRouteWithinStack( $route, $request ); } $response = $this->prepareResponse($request, $response); $this->callRouteAfter($route, $request, $response); return $response; } protected function runRouteWithinStack(Route $route, Request $request) { // 取得routes.php裏面的Middleware節點 $middleware = $this->gatherRouteMiddlewares($route); //這個有點眼熟 return (new Pipeline($this->container)) ->send($request) //執行上述的中間件 ->through($middleware) ->then(function($request) use ($route) { //到Controller類了 return $this->prepareResponse( $request, //run控制器 $route->run($request) ); }); } public function run(Request $request) { $this->container = $this->container ?: new Container; try { if ( ! is_string($this->action['uses'])) return $this->runCallable($request); if ($this->customDispatcherIsBound()) //其實是運行了這行 return $this->runWithCustomDispatcher($request); //其實我是直接想運行這行 return $this->runController($request); } catch (HttpResponseException $e) { return $e->getResponse(); } } //繼續調度,最終調度到.../Illuminate/Routing/ControllerDispatcher.php文件的dispatch方法 protected function runWithCustomDispatcher(Request $request) { list($class, $method) = explode('@', $this->action['uses']); $dispatcher = $this->container->make('illuminate.route.dispatcher'); return $dispatcher->dispatch($this, $request, $class, $method); }
文件清單…/Illuminate/Routing/ControllerDispatcher.php
public function dispatch(Route $route, Request $request, $controller, $method) { $instance = $this->makeController($controller); $this->assignAfter($instance, $route, $request, $method); $response = $this->before($instance, $route, $request, $method); if (is_null($response)) { //還要調度 $response = $this->callWithinStack( $instance, $route, $request, $method ); } return $response; } protected function callWithinStack($instance, $route, $request, $method) { //又是Middleware......有沒有忘記,官方文檔裏面Middleware能夠加在控制器的構造函數中!!沒錯,這個Middleware就是在控制器裏面申明的 $middleware = $this->getMiddleware($instance, $method); //又是這個,眼熟吧 return (new Pipeline($this->container)) ->send($request) //再次運行Middleware ->through($middleware) ->then(function($request) use ($instance, $route, $method) { 運行控制器,返回結果 return $this->call($instance, $route, $method); }); }
終於到達控制器