laravel的項目入口文件index.php以下php
1 define('LARAVEL_START', microtime(true)); 2 3 require __DIR__.'/../vendor/autoload.php'; 4 5 $app = require_once __DIR__.'/../bootstrap/app.php'; 6 7 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 8 9 $response = $kernel->handle( 10 $request = Illuminate\Http\Request::capture() 11 ); 12 13 $response->send(); 14 15 $kernel->terminate($request, $response);
第一句記錄了項目開始運行時間。laravel
第二句引入了基於composer的自動加載模塊。web
第三句引入了laravel應用主體。json
第四句建立了一個用於處理請求的核心。bootstrap
第五句對實例化後的request對象進行解析並返回執行後的response響應對象。數組
第六句將響應內容進行輸出。瀏覽器
第七句結束應用並釋放資源。緩存
關於第二句,這裏我先解釋一下自動加載,咱們都知道php中若是要使用文件外的代碼,須要使用require等方法先將文件引入,而後就可使用被引入那個文件的代碼了。可是咱們平時使用框架編寫代碼的時候就不須要這麼作了,只須要use命名空間,即可以直接new出對象了,這就要歸功於自動加載了,php在new出當前不存在的對象時,會觸發__autoload、spl_autoload等一些魔術方法。tp3的處理方式遍是很是粗暴的,在autoload魔術方法中,將當前類use的命名空間與咱們new的對象名進行字符串拼接,隨後require該文件就完了。laravel使用了composer顯然就高級的多,不過再怎麼高級,composer自己也是作了相似的操做,因此它也使用了spl_autoload函數,它高級在哪呢?咱們都知道composer使用時能夠新建一個json文件將須要的依賴編寫在裏面,composer運行時就會自動下載這些文件了。用composer 作自動加載也是同樣,它將json文件裏寫入的依賴進行緩存成了key/value的關聯數組,觸發spl_autoload函數的時候便根據這些映射來require。存放在laravel\vendor\composer\autoload_classmap.php文件內,有興趣的朋友可自行觀看,這裏不是重點,便到此爲止了。(我初學php面向對象的時候一直覺得命名空間下面那些use就是用來替代require、include的。直到後來學習mvc概念的時候本身試着作了個微框架的demo,才搞清楚use只是起到明確命名空間的做用。)閉包
咱們都知道,一般一個web程序所作的事,不外乎這麼幾點:mvc
一、用戶從瀏覽器進行請求,請求
二、程序接到請求開始運算,網頁程序
三、運算結果渲染成網頁返回給瀏覽器,網頁響應
咱們所寫的大量代碼都只是爲了更好、更快、更方便的去作這3件事而已。
index文件中的$request、$kernel、$response這三個變量就分別與這三點進行對應了。laravel中也將請求、響應、和計算進行了分離,請求部分使用syfmony的request組件對瀏覽器發出的請求頭等信息進行收集打包,造成一個對象來方便咱們操做。$kernel算是laravel的請求處理核心了,經過request裏的url找到相應路由的控制器,執行後返回視圖等響應,並將$response輸出至瀏覽器。知道大概流程後咱們來看laravel的核心部分。
第三句代碼就是在引入laravel的應用了,咱們跳到G:\wamp64\www\test\laravel55\bootstrap\app.php文件內,咱們會發現這個文件所作的事情也很少,只是new了一個application對象,調用了三次singleton方法,便將application實例給返回到index文件中了。(而這裏new對象的時候整個文件都沒有寫require等代碼,這就是經過composer進行的自動加載起做用了。)application文件位於G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Application.php,new application時傳入了當前的路徑到它的構造方法裏,它的構造方法執行了setBasePath、registerBaseBindings、registerBaseServiceProviders、registerCoreContainerAliases這幾個方法。
setBasePath:就是將各個系統關鍵類的路徑存儲在了app容器對象裏,跟蹤到bindPathsInContainer方法裏,咱們會發現以下所示的,存儲的路徑,具體的實現代碼在其父類Container類的instance方法中,代碼很簡單就一句是$this->instances[$abstract] = $instance;。你們能夠在Application類的setBasePath方法以後使用dd()打印一下$this看看它的instance屬性
1 protected function bindPathsInContainer() 2 { 3 $this->instance('path', $this->path()); 4 $this->instance('path.base', $this->basePath()); 5 $this->instance('path.lang', $this->langPath()); 6 $this->instance('path.config', $this->configPath()); 7 $this->instance('path.public', $this->publicPath()); 8 $this->instance('path.storage', $this->storagePath()); 9 $this->instance('path.database', $this->databasePath()); 10 $this->instance('path.resources', $this->resourcePath()); 11 $this->instance('path.bootstrap', $this->bootstrapPath()); 12 }
registerBaseBindings:這個方法作的事情和上一個差很少,將$this,application對象及它的父類存入了instance屬性中,分別起了app和Container兩個名字。將vendor路徑與bootstrap/cache/packages.php裏的providers服務提供者路徑傳入PackageManifest類中,並綁定在了app對象的instance中,你們在這個方法後dd($this)會發現跟setBasePath同樣,instance屬性中多了幾條,只是其中三個是對象而已。
registerBaseServiceProviders:註冊了基本的providers,event事件服務提供者、log日誌服務提供者、routing路由部分服務提供者。服務提供者的部分會在後面解釋,如今把它看作是一個功能模塊的入口就能夠了。一樣的,咱們在這個方法後面dd($this)會發先serviceProviders屬性與loadedProviders屬性增長了對應的值。bindings屬性也增長了provider相應的boot閉包,閉包中存儲的是實例化對象的代碼,運行後會獲得一個對象實例,以閉包的形式存儲來實現按需加載。
registerCoreContainerAliases:跟它的名字說的同樣,只是註冊了容器的核心類別名,一樣打印後發如今aliases、abstractAliases屬性中增長了相應的映射數組。之後會根據這個別名來方便的實例化對象,這個列表太長我就不放圖了
好的,總結一下,application類初始化的時候它作了這麼些工做:
一、設置路徑 二、綁定了app對象和packages包的實例 三、註冊了基本服務提供者 四、增長了核心類的別名 全都是一些配置工做。
好,回到app.php文件,$app執行了三個singleton方法,經過註釋咱們能夠知道它綁定了一些重要的接口道容器中。咱們點擊跳轉後一路跟蹤到G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Container\Container.php文件的bind方法中看着很長的代碼,其實都是狀態判斷,這個函數所作的事情仍是講傳入的類名路徑轉換爲一個啓動服務的閉包,並保存在容器的bindings屬性中。見下方代碼,getClosure方法也能夠看一下,比較簡單。
1 public function bind($abstract, $concrete = null, $shared = false) 2 { 3 //抽象類型判斷 4 $this->dropStaleInstances($abstract); 5 6 if (is_null($concrete)) { 7 $concrete = $abstract; 8 } 9 10 //這一階段重點,剛剛咱們index傳入的類路徑不是閉包,就會在這裏被getClosure方法轉換成一個返回對象實例的閉包了 11 if (! $concrete instanceof Closure) { 12 $concrete = $this->getClosure($abstract, $concrete); 13 } 14 //將閉包綁定在bindings屬性中 15 $this->bindings[$abstract] = compact('concrete', 'shared'); 16 17 // If the abstract type was already resolved in this container we'll fire the 18 // rebound listener so that any objects which have already gotten resolved 19 // can have their copy of the object updated via the listener callbacks. 20 if ($this->resolved($abstract)) { 21 $this->rebound($abstract); 22 } 23 }
咱們在app.php文件的singleton以後再次dd($app)會發現bindings屬性中多出了幾個相應的屬性,見下圖,其中http\kernel用來處理http請求,console\kernel用來處理artisan命令行請求,debug\exceptionHandler即是處理異常錯誤的了。
app.php文件看完了,咱們再回到index.php文件,第四行laravel製造了一個kernel實例,還記得剛剛在app.php文件時,咱們經過singleton綁定的那個閉包函數嗎?這裏立刻就派上用場了,make顧名思義就製造,這個方法經過類名路徑或別名返回一個對象實例(對,還記得剛剛application對象構造函數綁定了一大堆別名嗎)。G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Application.php類的make方法
1 public function make($abstract, array $parameters = []) 2 { 3 //這裏獲取了傳入類的別名,getAlias方法經過遞歸取出存儲在容器中的別名,不過如今kernel沒有別名因此仍是剛剛傳入的類路徑 4 $abstract = $this->getAlias($abstract); 5 //也不是延遲加載服務直接跳轉到父類make方法 6 if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) { 7 $this->loadDeferredProvider($abstract); 8 } 9 10 return parent::make($abstract, $parameters); 11 }
G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Container\Container.php在container類的make方法就開始從容器中解析類了,一開始那一大段都是檢測上下文綁定的,這個屬於契約接口的動態調用,暫時能夠不去看它,重點在於從getConcrete方法獲取到閉包後,直接進入了build構建方法。
public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); } protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); //是否存在構建上下文,此出爲了服務提供者的契約 $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); // If an instance of the type is currently being managed as a singleton we'll // just return an existing instance instead of instantiating new instances // so the developer can keep using the same objects instance every time. //instances數組中有該類,而且不須要構建上下文的話,便直接返回該類實例 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } //將實例化類所需的參數存入數組 $this->with[] = $parameters; //獲取該類閉包,若無則仍是返回類名字符串 $concrete = $this->getConcrete($abstract); // We're ready to instantiate an instance of the concrete type registered for // the binding. This will instantiate the types, as well as resolve any of // its "nested" dependencies recursively until all have gotten resolved. //若當前所make的類沒有上下文綁定,而且是一個閉包則直接進行構建,不然再次遞歸make方法得到契約所綁定類 if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // If we defined any extenders for this type, we'll need to spin through them // and apply them to the object being built. This allows for the extension // of services, such as changing configuration or decorating the object. foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // If the requested type is registered as a singleton we'll want to cache off // the instances in "memory" so we can return it later without creating an // entirely new instance of an object on each subsequent request for it. //若該類綁定時設置爲共享,則緩存至instances單例數組 if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); // Before returning, we will also set the resolved flag to "true" and pop off // the parameter overrides for this build. After those two things are done // we will be ready to return back the fully constructed class instance. $this->resolved[$abstract] = true; array_pop($this->with); return $object; }
還記得剛剛在app.php文件中有一個singleton函數將Illuminate\Contracts\Http\Kernel::class綁定爲了App\Http\Kernel::class類嗎?當build方法執行到kernel的構造函數時,跳轉到其父類G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php看一看
1 public function build($concrete) 2 { 3 // If the concrete type is actually a Closure, we will just execute it and 4 // hand back the results of the functions, which allows functions to be 5 // used as resolvers for more fine-tuned resolution of these objects. 6 //若傳入的是一個閉包則直接經過閉包實例化類,這種閉包通常由provider類在laravel應用初始化階段經過bind方法進行綁定。 7 if ($concrete instanceof Closure) { 8 return $concrete($this, $this->getLastParameterOverride()); 9 } 10 //製造一個類反射 11 $reflector = new ReflectionClass($concrete); 12 13 // If the type is not instantiable, the developer is attempting to resolve 14 // an abstract type such as an Interface of Abstract Class and there is 15 // no binding registered for the abstractions so we need to bail out. 16 if (! $reflector->isInstantiable()) { 17 return $this->notInstantiable($concrete); 18 } 19 //將當前所實例化的類存入棧 20 $this->buildStack[] = $concrete; 21 //得到該類構造方法 22 $constructor = $reflector->getConstructor(); 23 24 // If there are no constructors, that means there are no dependencies then 25 // we can just resolve the instances of the objects right away, without 26 // resolving any other types or dependencies out of these containers. 27 //構造函數沒有參數則直接實例化 28 if (is_null($constructor)) { 29 array_pop($this->buildStack); 30 31 return new $concrete; 32 } 33 //如有構造函數則獲取其參數 34 $dependencies = $constructor->getParameters(); 35 36 // Once we have all the constructor's parameters we can create each of the 37 // dependency instances and then use the reflection instances to make a 38 // new instance of this class, injecting the created dependencies in. 39 //運行構造函數,並解決依賴 40 $instances = $this->resolveDependencies( 41 $dependencies 42 ); 43 //解決完依賴,出棧 44 array_pop($this->buildStack); 45 46 return $reflector->newInstanceArgs($instances); 47 }
use Illuminate\Routing\Router; public function __construct(Application $app, Router $router) { $this->app = $app; //路由類實例,由容器自動加載依賴而來 $this->router = $router; //系統中間件 $router->middlewarePriority = $this->middlewarePriority; //中間件分組 foreach ($this->middlewareGroups as $key => $middleware) { $router->middlewareGroup($key, $middleware); } //註冊中間件別名 foreach ($this->routeMiddleware as $key => $middleware) { $router->aliasMiddleware($key, $middleware); } }
能夠看到,laravel在實例化出kernel對象的同時,經過kernel的構造函數加載了系統中間件,依賴了application與route兩個對象。並將自身的$middlewareGroups、routeMiddleware數組解析進了route對象裏,在路由進行調用的時候就會把路由方法上綁定的中間件名在這裏解析出實例來調用了,其中routeMiddleware爲別名所用。,隨後在index.php文件中立刻就利用kernel的handle方法,傳入了一個request對象,來處理此次的網頁url請求。
public function handle($request) { try { //啓用http方法覆蓋參數 $request->enableHttpMethodParameterOverride(); //經過路由發送請求 $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { $this->reportException($e); $response = $this->renderException($request, $e); } catch (Throwable $e) { $this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e); } $this->app['events']->dispatch( new Events\RequestHandled($request, $response) ); return $response; } protected function sendRequestThroughRouter($request) { //將請求存入容器 $this->app->instance('request', $request); //清除facade門面 Facade::clearResolvedInstance('request'); //初始化引導 $this->bootstrap(); //讓請求進入中間件 return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } //引導數組 protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ];
上面bootstrap中會分別執行每個bootstrapper的bootstrap方法來引導啓動應用程序的各個部分
1. DetectEnvironment 檢查環境
2. LoadConfiguration 加載應用配置
3. ConfigureLogging 配置日至
4. HandleException 註冊異常處理的Handler
5. RegisterFacades 註冊Facades
6. RegisterProviders 註冊Providers
7. BootProviders 啓動Providers
啓動應用程序的最後兩步就是註冊服務提供者和啓動提供者,先來看註冊服務提供器,服務提供器的註冊由類\Illuminate\Foundation\Bootstrap\RegisterProviders::class負責,該類用於加載全部服務提供器的 register 函數,並保存延遲加載的服務的信息,以便實現延遲加載。
全部服務提供器都在配置文件 app.php 文件的 providers 數組中。類 ProviderRepository 負責全部的服務加載功能:
loadManifest()會加載服務提供器緩存文件services.php,若是框架是第一次啓動時沒有這個文件的,或者是緩存文件中的providers數組項與config/app.php裏的providers數組項不一致都會編譯生成services.php。
application的registerConfiguredProviders()方法對服務提供者進行了註冊,經過框架的文件系統收集了配置文件中的各類provicers並轉化成數組,在G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php類的load方法中進行加載,但最終仍是會在application類中的register()方法中經過字符串的方式new出對象,在執行provider中自帶的register()方法
太多支線的細節不用深挖,重點在於讓請求進入中間件這裏,它用了一個管道模式,或者說裝飾模式,經過函數調用棧的形式,對請求進行過濾(這個等到後面中間件的時候單獨說)最終經過了全部中間件的請求會進入到Illuminate\Routing\router類的dispatchToRoute方法
router類裏的runRouteWithinStack方法經過管道的方式,運行了系統自帶中間件。這些中間件裏有一個laravel\framework\src\Illuminate\Routing\Middleware\SubstituteBindings.php中間件,用於處理路由上的綁定。其中調用了Illuminate\Routing\router類中的substituteImplicitBindings方法對路由上的模型進行了綁定。在Illuminate\Routing\RouteSignatureParameters.php中經過對路由route中的控制器字符串,或閉包函數,進行反射,獲取到他們的參數名,與類型提示,並過濾出Illuminate\Contracts\Routing\UrlRoutable類的子類,過濾後獲得的即是模型的類型提示了。以後又在Illuminate\Routing\ImplicitRouteBinding.php類中經過容器的make方法將反射獲得的類名實例化爲對象,使用model中的resolveRouteBinding方法經過路由參數獲取數據對象,然後在route類中賦值給route屬性。Illuminate\Routing\Route類的runCallable方法裏對路由進行了調用。控制器和方法是從路由文件中獲取到的(經過symfony的request對象獲取到pathinfo),依然是經過字符串解析爲類名和方法名,隨後經過ioc容器實例化類爲對象,再調用控制器基類的某個方法執行傳入的方法名
Illuminate\Routing\ControllerDispatcher類的dispatch方法爲真正執行的部分,其中resolveClassMethodDependencies方法會對控制器的參數實行依賴注入。傳入從路由中獲取的參數,與從控制器反射中獲取的方法參數。若是該方法所需的參數不是一個模型綁定,則會經過容器中的make方法獲取對象實例。
1 public function dispatch(Route $route, $controller, $method) 2 { 3 //解析類方法的依賴 4 $parameters = $this->resolveClassMethodDependencies( 5 $route->parametersWithoutNulls(), $controller, $method 6 ); 7 //若控制器中存在回調 8 if (method_exists($controller, 'callAction')) { 9 return $controller->callAction($method, $parameters); 10 } 11 //調用控制器方法 12 return $controller->{$method}(...array_values($parameters)); 13 }
最後,控制器返回執行後的結果,被response類包裝成響應對象返回至index.php,經過send方法發送至瀏覽器。