Laravel源碼分析--Laravel生命週期詳解

 

1、XDEBUG調試php

這裏咱們須要用到php的 xdebug 拓展,因此須要小夥伴們本身去裝一下,由於我這裏用的是docker,因此就簡單介紹下在docker中使用xdebug的注意點。html

一、在phpstorm中的 Perferences >> Languages & Framework >> PHP >> debug >> DBGp Proxy 中的Host填寫的是宿主機的IP地址。能夠在命令行中使用ifconfig / ipconfig查看你的本地IP。nginx

二、在 Perferences >> Languages & Framework >> PHP >> Servers 中將你本地項目地址映射到docker容器中的項目地址。laravel

三、xdeug.ini的配置。須要特別注意的就是  xdebug.remote_host  填的也是你的宿主機ip。 xdebug.remote_connect_back 配置必定要關掉,不然 xdebug.remote_host 就會不起做用。web

當xdebug啓動後,效果是這樣的docker

 

 

2、Laravel生命週期json

一、啓動容器bootstrap

咱們知道Laravel全部的請求都會被nginx轉發到項目下的 public/index.php 文件中。數組

<?php /** * Laravel - A PHP Framework For Web Artisans * * @package Laravel * @author Taylor Otwell <taylor@laravel.com> */

define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- | Register The Auto Loader |-------------------------------------------------------------------------- | | Composer provides a convenient, automatically generated class loader for | our application. We just need to utilize it! We'll simply require it | into the script here so that we don't have to worry about manual | loading any of our classes later on. It feels great to relax. | */

require __DIR__.'/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- | Turn On The Lights |-------------------------------------------------------------------------- | | We need to illuminate PHP development, so let us turn on the lights. | This bootstraps the framework and gets it ready for use, then it | will load up this application so that we can run it and send | the responses back to the browser and delight our users. | */

$app = require_once __DIR__.'/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); $kernel->terminate($request, $response);

很顯然,第一件事就是 require __DIR__.'/../vendor/autoload.php' ,自動加載的加載。閉包

核心 vendor/composer/ClassLoader 類中的 findFIle 方法

public function findFile($class) { // class map lookup
        if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist.
            $this->missingClasses[$class] = true; } return $file; }

 

當自動加載註冊完後,就開始啓動Laravel了,下面 $app = require_once __DIR__.'/../bootstrap/app.php' 就是返回一個App實例,咱們看下這文件到底作了些啥。

<?php/* |-------------------------------------------------------------------------- | Create The Application |-------------------------------------------------------------------------- | | The first thing we will do is create a new Laravel application instance | which serves as the "glue" for all the components of Laravel, and is | the IoC container for the system binding all of the various parts. | */

$app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); /* |-------------------------------------------------------------------------- | Bind Important Interfaces |-------------------------------------------------------------------------- | | Next, we need to bind some important interfaces into the container so | we will be able to resolve them when needed. The kernels serve the | incoming requests to this application from both the web and CLI. | */

$app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class ); /* |-------------------------------------------------------------------------- | Return The Application |-------------------------------------------------------------------------- | | This script returns the application instance. The instance is given to | the calling script so we can separate the building of the instances | from the actual running of the application and sending responses. | */

return $app;

首先new出一個app實例,但在其構造函數中,作了一些框架的初始化工做。

public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }

在 setBasePath 中,向Laravel容器中注入了下面這些路徑。

protected function bindPathsInContainer() { $this->instance('path', $this->path()); $this->instance('path.base', $this->basePath()); $this->instance('path.lang', $this->langPath()); $this->instance('path.config', $this->configPath()); $this->instance('path.public', $this->publicPath()); $this->instance('path.storage', $this->storagePath()); $this->instance('path.database', $this->databasePath()); $this->instance('path.resources', $this->resourcePath()); $this->instance('path.bootstrap', $this->bootstrapPath()); }

這也就是爲何咱們在項目中可使用 storage_path() 等方法獲取路徑的緣由。

而後就是註冊一些Laravel的基礎綁定

protected function registerBaseBindings() { static::setInstance($this); $this->instance('app', $this); $this->instance(Container::class, $this); $this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() )); }

這裏將app容器綁定到 app 和 Container::class 上,而後再綁定一個 PackageManifest::class 到容器中,這個類是在Laravel5.5後的新功能--包自動發現時用的。

而後註冊了三個服務提供者,若是它們中有register方法,則執行其中的register方法。

   protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); }

至於它們的register方法都作了些啥,感興趣的同窗能夠去看下源碼。最後 registerCoreContainerAliases() 方法就是給大多類註冊了一些別名/簡稱。

以上這些動做咱們均可以經過xdebug來看到。讓咱們看下app實例中如今都有那些動做已經完成了。

這是上面 registerBaseServiceProviders() 註冊的三個服務提供者。

這個是上面註冊的三個服務提供者裏面register方法綁定到容器中的。

這是 registerBaseBindings 方法中綁定。

這兩個屬性是 registerCoreContainerAliases 方法中綁定的一些別名/簡稱。

而後連續三次調用 $app->singleton() ,分別將http,console和異常處理的實現類綁定到容器中。能夠在app實例的bindings屬性中看到

而後返回app實例,到此,這就是 bootstrap/app.php 文件作的全部事情。

咱們知道Laravel的核心就是服務容器,在 bootstrap/app.php 文件中,咱們向容器中綁定了不少服務,那麼,咱們想要服務該怎麼從容器中取呢?

回到 public/index.php 中,緊接着 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); ,就是從容器中取出 Illuminate\Contracts\Http\Kernel::class 類,此時,咱們已經知道, Illuminate\Contracts\Http\Kernel::class 的實現類 App\Http\Kernel::class ,咱們也能夠在xdebug中看到。

這裏試想下,若是不用服務容器,本身new一個App\Http\Kernel類出來,會發現裏面有不少依賴,即便你最後new出來了,相比服務容器,那也太麻煩了。

服務容器是使用依賴注入和 ReflectionClass 反射類實現的。

App\Http\Kernel父類的構造方法中就是將App\Http\Kernel類中$middlewareGroups和$routeMiddleware數組中的中間件綁定到Route::class中。

 

二、分發路由

後面調用的 $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); 像一個大黑盒子同樣,在裏面完成了全部的路由分發,中間件檢測等工做。

先看一下 $request = Illuminate\Http\Request::capture() 方法;首先是經過php的超全局變量建立一個SymfonyRequest,而後再將SymfonyRequest轉換Laravel可用的 Illuminate\Http\Request ,並丟入 handle() 方法中。

   /** * Handle an incoming HTTP request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */
    public function handle($request) { try { $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; }

裏面的核心就是 sendRequestThroughRouter() 方法,經過字面意思也能夠知道是--經過路由發送請求。

/** * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */
    protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }

 $this->app->instance('request', $request); 和 Facade::clearResolvedInstance('request'); 分別表示綁定 Illuminate\Http\Request 到容器中和從Facade(目前並無)中刪除request。

 $this->bootstrap(); 是將 Illuminate\Foundation\Http\Kernel 中的 $bootstrappers 數組

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, ];

放到app容器中的 $this->app->bootstrapWith($this->bootstrappers()); 方法中一一執行。

public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); } }

該方法首先觸發 'bootstrapping: '.$bootstrapper 事件,代表 $bootstrapper 正在執行。當執行完 $bootstrapper 中的 bootstrap() 的方法後,觸發 'bootstrapped: '.$bootstrapper 時事件,代表 $bootstrapper 執行完畢。下面咱們看下這個數組中到底作了什麼。

一、 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 很顯然,就是加載.env文件中的配置到項目中,它這裏放在三個地方。

因此當咱們想要獲取.env中的配置文件時,可使用getenv()、$_ENV$_SERVER均可以。

二、 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 一樣的,這是加載config文件下的全部配置文件。並將這些配置存在 Illuminate\Config\Repository 類的 $items 中。

三、 \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 加載異常處理類

四、 \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 註冊  config/app.php  中的 aliases 數組和 bootstrap/cache/packages.php 的全部門面。

五、 \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 註冊 config/app.php 中的 providers 和 bootstrap/cache/packages.php 中的全部服務提供者,並將即便加載的和延遲加載的分開。記住,註冊服務提供者時,若是該服務提供者有 register() 方法,則執行。

六、 \Illuminate\Foundation\Bootstrap\BootProviders::class, 若是存在的話,則執行上一步中註冊的全部服務提供者中的 boot() 方法。

執行完 bootstrap() 方法。Laravel全部的加載工做都完成了,後面就開始經過 Pipeline 分發路由了。

return (new Pipeline($this->app)) ->send($request) // 設置須要分發的request ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)  // 設置request須要經過的中間件 ->then($this->dispatchToRouter());  // 開始經過中間件

重點就是在這個then()方法中。

/** * Run the pipeline with a final destination callback. * * @param \Closure $destination * @return mixed */
    public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }

形象點,能夠用包洋蔥來理解這裏到底作了些什麼?

 array_reduce() 裏就是將 App\Http\Kernel 中 $middleware 數組倒過來,經過 $this->carry() 將 $this->prepareDestination($destination) 像包洋蔥同樣一層層包起來。

能夠經過xdebug看下最終包好的洋蔥的樣子。

洋蔥包完了,咱們就該剝洋蔥了。 return $pipeline($this->passable); 就是剝洋蔥的整個動做。

具體怎麼個剝法,能夠在 carry() 方法中看到

protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if (is_callable($pipe)) { // If the pipe is an instance of a Closure, we will just call it directly but // otherwise we'll resolve the pipes out of the container and call it with // the appropriate method and arguments, returning the results back out.
                    return $pipe($passable, $stack); } elseif (! is_object($pipe)) {
            // $pipe是中間件的類名因此會進入這裏,parsePipeString方法是解析中間件有沒有攜帶參數,若是沒有則$parameters是一個空數組。
list($name, $parameters) = $this->parsePipeString($pipe); // If the pipe is a string we will parse the string and resolve the class out // of the dependency injection container. We can then build a callable and // execute the pipe function giving in the parameters that are required. $pipe = $this->getContainer()->make($name);    // 從容器中解析出中間件類             
            // $passable就是request, $stack就是包洋蔥時包進來的閉包  
$parameters = array_merge([$passable, $stack], $parameters); } else { // If the pipe is already an object we'll just make a callable and pass it to // the pipe as-is. There is no need to do any extra parsing and formatting // since the object we're given was already a fully instantiated object. $parameters = [$passable, $stack]; }
          // 若是中間件中有$this->method方法(其實就是handle方法),則傳入$parameters參數,執行handle方法。
$response = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); return $response instanceof Responsable ? $response->toResponse($this->container->make(Request::class)) : $response; }; };

一直到洋蔥芯,其餘外層都是中間件的處理,咱們找其中一個看一下,例如 \App\Http\Middleware\CheckForMaintenanceMode::class, 的handle方法。

   /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */
    public function handle($request, Closure $next) {
     // 判斷項目是否下線
if ($this->app->isDownForMaintenance()) { $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);        // 驗證IP白名單 if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) { return $next($request); }        // 驗證path是否在 $except 數組中 if ($this->inExceptArray($request)) { return $next($request); } throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']); }      // 若是經過,則傳request到閉包繼續執行 return $next($request); }

全部的中間件處理方式都是同樣的,具體哪一個中間件作了哪些操做,感興趣的同窗能夠本身去看下源碼,這裏不詳細贅述。下面咱們直接看洋蔥芯都作了些啥?

其實洋蔥芯就是 dispatchToRouter() 方法中返回的一個閉包。

   /** * Get the route dispatcher callback. * * @return \Closure */
    protected function dispatchToRouter() { return function ($request) {
       // 向容器中放入實例request
$this->app->instance('request', $request);
       // 經過
\Illuminate\Routing\Router的dispatch方法分發request
       return $this->router->dispatch($request); };
   }

這個閉包裏就是分發路由,而後再次像以前包洋蔥和剝洋蔥同樣驗證request中間件組裏的中間件和路由配置裏的中間件,最後執行控制器對應的方法。

至此Laravel整個生命週期就結束了。撒花🎉,下篇將簡單介紹下Lumen框架的生命週期和解釋下Lumen到底比Laravel輕在哪裏?

原文出處:https://www.cnblogs.com/johnson108178/p/10045745.html

相關文章
相關標籤/搜索