laravel的項目入口文件index.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);
第一句記錄了項目開始運行時間。
第二句引入了基於composer的自動加載模塊。
第三句引入了laravel應用主體。
第四句創建了一個用於處理請求的核心。
第五句對實例化后的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程序所做的事,不外乎這么幾點:
1、用戶從瀏覽器進行請求,請求
2、程序接到請求開始運算,網頁程序
3、運算結果渲染成網頁返回給瀏覽器,網頁響應
我們所寫的大量代碼都只是為了更好、更快、更方便的去做這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類初始化的時候它做了這么些工作:
1、設置路徑 2、綁定了app對象和packages包的實例 3、注冊了基本服務提供者 4、增加了核心類的別名 全都是一些配置工作。
好,回到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方法發送至瀏覽器。