laravel生命周期

简介

一、Composer 自动加载项目依赖

二、创建应用实例

创建容器

绑定内核

HTTP 内核类

Console 内核

绑定异常处理

三、接收请求并响应

解析内核

处理 HTTP 请求

发送响应

四、终止应用程序

五、总结

References

Laravel的生命周期开始于 public/index.php,结束于 public/index.php。

客户端的所有请求都经由Web服务器引导到这个文件中。

以下是public/index.php 文件的源码和注释:

#public/index.php 文件

// 定义了laravel一个请求的开始时间

define('LARAVEL_START', microtime(true));

// composer自动加载机制,加载项目依赖

require __DIR__.'/../vendor/autoload.php';

// 这句话你就可以理解laravel,在最开始引入了一个ioc容器。

$app = require_once __DIR__.'/../bootstrap/app.php';

// 这个相当于我们创建了Kernel::class的服务提供者

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 获取一个 Request ,返回一个 Response。以把该内核想象作一个代表整个应用的大黑盒子,输入 HTTP 请求,返回 HTTP 响应

// 处理请求

$response = $kernel->handle(

    // 创建请求实例

    $request = Illuminate\Http\Request::capture()

);

// 发送响应,就是把我们服务器的结果返回给浏览器。

$response->send();

// 终止程序,这个就是执行我们比较耗时的请求,

$kernel->terminate($request, $response);

由上可知 Laravel 的生命周期分为以下3个主要阶段:

加载项目依赖。

创建Laravel应用实例

接收请求并响应。

接下来我们根据 public/index.php 文件,开始 Laravel 的生命周期之旅吧!

一、Composer 自动加载项目依赖

// composer自动加载机制

require __DIR__.'/../vendor/autoload.php';


Composer 是 PHP 的包管理器,用来管理项目依赖的工具,autoload.php 中引入了 Composer 自动生成的加载程序,实现加载第三方依赖。

autoload.php 文件代码:

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit101671ca9bbc2f62f8335eb842637291::getLoader();

二、创建应用实例

$app = require_once __DIR__.'/../bootstrap/app.php';

bootstrap/app.php 文件代码:

// 创建Laravel应用的ioc容器,

$app = new Illuminate\Foundation\Application(

    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)

);

// 完成内核的绑定

$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 $app;

如果你理解了 Ioc(控制反转),那么对于以上代码你一定很熟悉。

Ioc 容器(服务容器) 是 Laravel 的核心,这一阶段主要实现了 2大功能:

1、创建容器

2、绑定内核

创建容器

$app = new Illuminate\Foundation\Application(

    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)

);

我们进入 Illuminate\Foundation\Application 容器中,以下是构造函数的分析:

/**

    * Create a new Illuminate application instance.

    *

    * @param  string|null  $basePath

    * @return void

    */

    public function __construct($basePath = null)

    {

        if ($basePath) {

            // 1、路径绑定

            $this->setBasePath($basePath);

        }

// 2、基础绑定

        $this->registerBaseBindings();

        // 3、基础服务提供者绑定(事件,日志,路由)

        $this->registerBaseServiceProviders();

        // 4、核心别名绑定,目的是给核心的类命名空间设置别名,以便后续使用

        $this->registerCoreContainerAliases();

    }


绑定内核

// 完成内核的绑定

// HTTP内核

$app->singleton(

    Illuminate\Contracts\Http\Kernel::class,

    App\Http\Kernel::class

);

// Console内核

$app->singleton(

    Illuminate\Contracts\Console\Kernel::class,

    App\Console\Kernel::class

);

// 绑定异常处理

$app->singleton(

    Illuminate\Contracts\Debug\ExceptionHandler::class,

    App\Exceptions\Handler::class

);


在 Laravel 中只要是通过 public/index.php 来启动框架的,都会用到 Http Kernel(主要作用就是接受请求并返回响应),而其他的例如通过 artisan 命令、计划任务、队列等启动框架进行处理的都会用到 Console 内核。

其中 HTTP 内核类继承 Illuminate\Foundation\Http\Kernel ,HTTP内核类中定义了中间件相关数组,中间件主要是提供了一种方便机制用来过滤进入应用的请求和处理HTTP响应。

HTTP 内核类:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel

{

    /**

    * The application's global HTTP middleware stack.

    *

    * These middleware are run during every request to your application.

    *

    * @var array

    */

    protected $middleware = [

        \App\Http\Middleware\TrustProxies::class,

        \App\Http\Middleware\CheckForMaintenanceMode::class,

        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,

        \App\Http\Middleware\TrimStrings::class,

        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,

    ];

    /**

    * The application's route middleware groups.

    *

    * @var array

    */

    protected $middlewareGroups = [

        'web' => [

            \App\Http\Middleware\EncryptCookies::class,

            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

            \Illuminate\Session\Middleware\StartSession::class,

            // \Illuminate\Session\Middleware\AuthenticateSession::class,

            \Illuminate\View\Middleware\ShareErrorsFromSession::class,

            \App\Http\Middleware\VerifyCsrfToken::class,

            \Illuminate\Routing\Middleware\SubstituteBindings::class,

        ],

        'api' => [

            'throttle:60,1',

            'bindings',

        ],

    ];

    /**

    * The application's route middleware.

    *

    * These middleware may be assigned to groups or used individually.

    *

    * @var array

    */

    protected $routeMiddleware = [

        'auth' => \App\Http\Middleware\Authenticate::class,

        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,

        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,

        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

        'can' => \Illuminate\Auth\Middleware\Authorize::class,

        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,

        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,

        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,

    ];

    /**

    * The priority-sorted list of middleware.

    *

    * This forces non-global middleware to always be in the given order.

    *

    * @var array

    */

    protected $middlewarePriority = [

        \Illuminate\Session\Middleware\StartSession::class,

        \Illuminate\View\Middleware\ShareErrorsFromSession::class,

        \App\Http\Middleware\Authenticate::class,

        \Illuminate\Session\Middleware\AuthenticateSession::class,

        \Illuminate\Routing\Middleware\SubstituteBindings::class,

        \Illuminate\Auth\Middleware\Authorize::class,

    ];

}

HTTP内核类的父类:Illuminate\Foundation\Http\Kernel 提供了名为 $bootstrappers 的引导程序数组,包括了环境检测、加载配置、处理异常、Facades注册、服务提供者注册、启动服务这6个 程序。

/**

    * The bootstrap classes for the application.

    *

    * @var array

    */

    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,

    ];

Console 内核:

一个健壮的应用程序除了满足网络请求外,还应该包括执行计划任务、异步队列等,Laravel中通过artisan工具定义各种命令来完成非 HTTP 请求的各种场景。artisan命令通过 Laravel 的 Console 内核完成对应用核心组件的调度来完成任务。

Console 内核在这不做详细介绍。

绑定异常处理

异常处理由 App\Exceptions\Handler 类完成,所有的异常处理都由其处理,在这里同样不做详细介绍。

三、接收请求并响应

解析内核

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

1

这里将 HTTP 内核解析出来,我们进一步查看Illuminate\Contracts\Http\Kernel::class 的内部代码,

构造函数中注入了app容器和路由器2个函数,在实例化内核的过程中,将内核中定义的中间件注册到路由器中,注册完成后便可以处理HTTP请求前调用中间件 实现过滤请求的目的。

Illuminate\Foundation\Http\Kernel 的构造函数:

/**

    * Create a new HTTP kernel instance.

    *

    * @param  \Illuminate\Contracts\Foundation\Application  $app

    * @param  \Illuminate\Routing\Router  $router

    * @return void

    */

    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);

        }

    }

// Illuminate\Routing\Router 类中

/**

    * Register a group of middleware.

    *

    * @param  string  $name

    * @param  array  $middleware

    * @return $this

    */

    public function middlewareGroup($name, array $middleware)

    {

        $this->middlewareGroups[$name] = $middleware;

        return $this;

    }

/**

    * Register a short-hand name for a middleware.

    *

    * @param  string  $name

    * @param  string  $class

    * @return $this

    */

    public function aliasMiddleware($name, $class)

    {

        $this->middleware[$name] = $class;

        return $this;

    }

处理 HTTP 请求

// handle 方法处理请求

$response = $kernel->handle(

    // 创建请求实例

    $request = Illuminate\Http\Request::capture()

);

创建请求实例:在处理请求过程之前会通过 Illuminate\Http\Request 的 capture 方法,以进入应用的HTTP请求信息为基础,创建出一个 Laravel Request请求实例,在后续应用剩余的生命周期中Request请求实例就是对本次HTTP请求的抽象。

/**

    * Create a new Illuminate HTTP request from server variables.

    *

    * @return static

    */

    public static function capture()

    {

        static::enableHttpMethodParameterOverride();

        return static::createFromBase(SymfonyRequest::createFromGlobals());

    }

/**

    * Creates a new request with values from PHP's super globals.

    *

    * @return static

    */

    public static function createFromGlobals()

    {

        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')

            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])

        ) {

            parse_str($request->getContent(), $data);

            $request->request = new ParameterBag($data);

        }

        return $request;

    }

handle 处理请求:将 HTTP 请求抽象成 Laravel Request 请求实例后,请求实例会被传导进入到HTTP内核的 handle 方法内部,请求的处理就是由 handle 方法来完成的。

namespace Illuminate\Foundation\Http;

class Kernel implements KernelContract

{

    /**

    * 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;

    }

}

handle 方法接收了一个请求,并在最后返回了一个 HTTP响应。

接下来进入 sendRequestThroughRouter 方法,通过中间件/路由器发送给定的请求。

该方法的程序执行如下:

1、将 request 请求实例注册到 app 容器当中

2、清除之前的 request 实例缓存

3、启动引导程序:加载内核中定义的引导程序来引导启动应用

4、将请求发送到路由:通过 Pipeline 对象传输 HTTP请求对象流经框架中定义的HTTP中间件和路由器来完成过滤请求,最终将请求传递给处理程序(控制器方法或者路由中的闭包)由处理程序返回相应的响应。

/**

    * 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());

    }


bootstrap 引导程序

/**

    * Bootstrap the application for HTTP requests.

    *

    * @return void

    */

    public function bootstrap()

    {

        if (! $this->app->hasBeenBootstrapped()) {

            $this->app->bootstrapWith($this->bootstrappers());

        }

    }

/**

    * Get the bootstrap classes for the application.

    *

    * @return array

    */

    protected function bootstrappers()

    {

        return $this->bootstrappers;

    }

/**

    * The bootstrap classes for the application.

    *

    * @var array

    */

    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,

    ];

/*引导启动Laravel应用程序

1. DetectEnvironment  检查环境

2. LoadConfiguration  加载应用配置

3. ConfigureLogging  配置日至

4. HandleException    注册异常处理的Handler

5. RegisterFacades    注册Facades

6. RegisterProviders  注册Providers

7. BootProviders      启动Providers

*/

关于引导程序的启动原理,有时间我们再抽出来看看。

发送响应

$response->send();

1

经过了以上阶段,终于获取到了我们想要的数据,接下来是将数据响应到客户端。

程序由在 Illuminate\Http\Response 内部由其父类 Symfony\Component\HttpFoundation\Response 的 send() 方法实现。

/**

    * Sends HTTP headers and content.

    *

    * @return $this

    */

    public function send()

    {

        $this->sendHeaders(); // 发送头部信息

        $this->sendContent(); // 发送报文主题

        if (\function_exists('fastcgi_finish_request')) {

            fastcgi_finish_request();

        } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {

            static::closeOutputBuffers(0, true);

        }

        return $this;

    }

四、终止应用程序

该过程调用终止中间件。

响应完成后,HTTP 内核会调用 terminate 中间件做一些后续的处理工作。比如,Laravel 内置的 session 中间件会在响应发送到浏览器之后将会话数据写入存储器中。

HTTP 内核的 terminate 方法会调用 terminate 中间件的 terminate 方法,调用完成后,整个生命周期结束。

$kernel->terminate($request, $response);

1

/**

    * Call the terminate method on any terminable middleware.

    *

    * @param  \Illuminate\Http\Request  $request

    * @param  \Illuminate\Http\Response  $response

    * @return void

    */

    public function terminate($request, $response)

    {

        $this->terminateMiddleware($request, $response);

        $this->app->terminate();

    }

/**

    * Call the terminate method on any terminable middleware.

    *

    * @param  \Illuminate\Http\Request  $request

    * @param  \Illuminate\Http\Response  $response

    * @return void

    */

    protected function terminateMiddleware($request, $response)

    {

        $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(

            $this->gatherRouteMiddleware($request),

            $this->middleware

        );

        foreach ($middlewares as $middleware) {

            if (! is_string($middleware)) {

                continue;

            }

            [$name] = $this->parseMiddleware($middleware);

            $instance = $this->app->make($name);

            if (method_exists($instance, 'terminate')) {

                $instance->terminate($request, $response);

            }

        }

    }

五、总结

在Laravel 的整个生命周期中,加载项目依赖、创建应用实例、接收并响应请求,终止程序,内核都起到了串联作用。

首先,创建 Laravel 应用程序 阶段时,包含了注册项目基础服务、注册项目服务提供者别名、注册目录路径、异常类绑定等工作,同时在HTTP 内核中配置了引导程序。

接着,在 接收请求并响应 阶段时,会根据运行的环境解析出 HTTP 内核或 Console 内核。在 HTTP 内核中把中间件注册到路由器中。

然后,处理请求阶段的过程中,将请求的实例注册到app容器中,通过引导程序启动应用,最后发送请求到路由。

之后,通过Response类响应数据。

最后,通过terminate 方法终止程序。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容

  • 世间万物皆有生命周期,当我们使用任何工具时都需要理解它的工作原理,那么用起来就会得心应手,应用开发也是如此。理解了...
    伊Summer阅读 12,497评论 6 36
  • 首先: Laravel 应用的所有请求入口都是 public/index.php 文件。而所有的请求都是经由你的 ...
    如梦又似幻阅读 370评论 0 0
  • laravel 的源代码生命周期是什么样子的 第一步 Laravel 应用的所有请求入口都是 public/ind...
    alex2016阅读 893评论 0 0
  • 入口文件 indx.php 进入 bootstrap/app.php 代码深入 上面代码做了以下的几件事 设置ap...
    杰森跟班阅读 361评论 0 0
  • Laravel是我最熟悉的框架,却一直没认真的梳理过它的源码,甚是遗憾,遂在此梳理一遍laravel的源码。 请求...
    岳凯阅读 795评论 0 1