Laravel 的启动流程的详细介绍(精华)

2020-02-19 23:10:51

参考地址 Laravel 的启动流程

Laravel 的启动流程

很多人说 laravel 框架比较慢,比较臃肿,我们这次就来了解一下 laravel 启动过程中,需要做哪些事情,这次以 laravel5.8 为例


入口文件

require __DIR__.'/../vendor/autoload.php'; //注册自动加载函数

$app = require_once __DIR__.'/../bootstrap/app.php'; //实例化容器

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

$response = $kernel->handle(

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

);

$response->send();

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

1. 实例化得到框架容器 $app

$app = new Illuminate\Foundation\Application(

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

);


class Application extends Container implements ApplicationContract, HttpKernelInterface

//这个application类就是我们的$app类,继承了容器类,看看实例化的时候做了哪些操作

public function __construct($basePath = null)

    {

        if ($basePath) {

            $this->setBasePath($basePath);

        }

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();

    }

1.1 绑定路径到容器中


1.2 绑定基础类库到容器中


protected function registerBaseBindings()

    {

        static::setInstance($this);

        $this->instance('app', $this);

        $this->instance(Container::class, $this);

        $this->singleton(Mix::class);

        $this->instance(PackageManifest::class, new PackageManifest(

            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()

        ));

    }

1.3 注册三个服务(需要实例化三个服务)


protected function registerBaseServiceProviders()

    {

        $this->register(new EventServiceProvider($this)); //事件服务

        $this->register(new LogServiceProvider($this)); //日志服务

        $this->register(new RoutingServiceProvider($this)); //路由服务

    }

1.4 把别名数组注入到 $app 属性 $this->aliases 中


总结一下实例化 $app 的开销


1. 用容器做了很多的绑定,

这个只是吧接口的映射关系写入到容器中,没有什么消耗


2. 实例化了一个 packageManifest 类,实例化 Filesystem 类,实例化了三个服务类。

packageManifest 类是 Laravel5.5 增加的新功能 —— 包自动发现


三个服务类的实例化过程也仅仅是做一些参数注入,开销也不大。


3. 注册了三个服务

public function register($provider, $force = false)

    {

        if (($registered = $this->getProvider($provider)) && ! $force) { //判断服务是否注册过

            return $registered;

        }

        if (is_string($provider)) {

            $provider = $this->resolveProvider($provider);

        }

        $provider->register(); //执行服务的register方法

        if (property_exists($provider, 'bindings')) {

            foreach ($provider->bindings as $key => $value) {

                $this->bind($key, $value);

            }

        }

        if (property_exists($provider, 'singletons')) {

            foreach ($provider->singletons as $key => $value) {

                $this->singleton($key, $value);

            }

        }

        $this->markAsRegistered($provider);//写入到一个数组中,标识已经注册过的服务

        if ($this->booted) {

            $this->bootProvider($provider);

        }

        return $provider;

    }

可以看到,注册服务的主要开销就是服务本身的 register 方法。可以看看 event 服务的注册方法


public function register()

    {

        $this->app->singleton('events', function ($app) {

            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {

                return $app->make(QueueFactoryContract::class);

            });

        });

    }

也只是把一个闭包绑定到容器在中,只有获取 events 实例时才会执行闭包,所以这个注册也是消耗很小的。


4. 绑定三个重要接口到容器中

$app->singleton(

    Illuminate\Contracts\Http\Kernel::class,

    App\Http\Kernel::class  //http处理的核心

);

$app->singleton(

    Illuminate\Contracts\Console\Kernel::class,

    App\Console\Kernel::class //后台处理核心

);

$app->singleton(

    Illuminate\Contracts\Debug\ExceptionHandler::class,

    App\Exceptions\Handler::class //异常处理核心

);

单纯的绑定动作是没什么消耗的。


2. 从容器获取 http 核心 kernel

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

make 的方法就是从容器获取接口绑定的实例,并且会把这个实例的所有依赖实例化注入进来。实质就是实例化 App\Http\Kernel::class,我们看看它的构造函数


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

        }

    }

开销总结


1. 实例化了路由 Illuminate\Routing\Router 类,还有路由实例的依赖 Dispatcher,RouteCollection,Container

 public function __construct(Dispatcher $events, Container $container = null)

    {

        $this->events = $events;

        $this->routes = new RouteCollection;

        $this->container = $container ?: new Container;

    }

其实就是 Illuminate\Events\Dispatcher,Illuminate\Routing\RouteCollection 和 container 类。


2. 把中间件仓库数组注入到 router 的中间件属性中

public function middlewareGroup($name, array $middleware)

    {

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

        return $this;

    }

3. 获取 request, 实质是从全局变量 $_GET 等获取数据,封装成对象

$response = $kernel->handle(

    $request = Illuminate\Http\Request::capture() //Request是静态类

);

1. 从服务器变量获取 request

public static function capture()

    {

        static::enableHttpMethodParameterOverride();

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

    }


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;

    }

我们可以发现这个 $SymfonyRequest 是通过 createRequestFromFactory (), 传入 php 全局变量获取的


public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)

    {

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

        $this->query = new ParameterBag($query);

        $this->attributes = new ParameterBag($attributes);

        $this->cookies = new ParameterBag($cookies);

        $this->files = new FileBag($files);

        $this->server = new ServerBag($server);

        $this->headers = new HeaderBag($this->server->getHeaders());

        $this->content = $content;

        $this->languages = null;

        $this->charsets = null;

        $this->encodings = null;

        $this->acceptableContentTypes = null;

        $this->pathInfo = null;

        $this->requestUri = null;

        $this->baseUrl = null;

        $this->basePath = null;

        $this->method = null;

        $this->format = null;

    }

其实就是通过多个全局变量组装而成。$_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER 通过一定的格式组装成为我们的 $request。


4. 调用 http 核心 $kernel 实例的 handle 方法,得到 $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;

    }

//这个就是传入$request实例,得到$response实例的方法

protected function sendRequestThroughRouter($request)

    {

        $this->app->instance('request', $request); //向容器注册request实例

        Facade::clearResolvedInstance('request');//删除Facede的静态属性保存的request实例

        $this->bootstrap();//启动框架的附属的类工具

        return (new Pipeline($this->app))

                    ->send($request)

                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)

                    ->then($this->dispatchToRouter());

    }

1.laravel 自带了一些 bootstrap 工具类,$this->bootstrap () 就是执行这些

 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,

    ];


public function bootstrap()

    {

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

            $this->app->bootstrapWith($this->bootstrappers()); //通过app的bootstrapWith方法

        }

    }

//这个就是Illuminate\Foundation\Application的bootstrapWith方法

public function bootstrapWith(array $bootstrappers)

    {

        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {

            $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);//监听事件

            $this->make($bootstrapper)->bootstrap($this);//步骤二

            $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);//监听事件

        }

    }

这个才是框架最大的开销,有六个 Bootstrap 类,因为有了它们,我们才能使用门面,注册服务提供者等功能。我们可以看一下每个 Bootstarp 类是如何执行的。首先 $this ['event'] 就是 Illuminate\Events\Dispatcher 类。


1.1 分析一下 Illuminate\Events\Dispatcher 类的 dispatch 方法 就是监听事件

public function dispatch($event, $payload = [], $halt = false)

    {

        // When the given "event" is actually an object we will assume it is an event

        // object and use the class as the event name and this event itself as the

        // payload to the handler, which makes object based events quite simple.

        [$event, $payload] = $this->parseEventAndPayload(

            $event, $payload

        );


        if ($this->shouldBroadcast($payload)) {

            $this->broadcastEvent($payload[0]);//$payload[0]就是$app

        }


        $responses = [];

        foreach ($this->getListeners($event) as $listener) {

            $response = $listener($event, $payload);

            // If a response is returned from the listener and event halting is enabled

            // we will just return this response, and not call the rest of the event

            // listeners. Otherwise we will add the response on the response list.

            if ($halt && ! is_null($response)) {

                return $response;

            }

            // If a boolean false is returned from a listener, we will stop propagating

            // the event to any further listeners down in the chain, else we keep on

            // looping through the listeners and firing every one in our sequence.

            if ($response === false) {

                break;

            }

            $responses[] = $response;

        }

        return $halt ? null : $responses;

    }

这部分可以去看 laravel 事件的原理,Dispatcher 方法就是监听事件,第一个参数是事件名称,所以每一个 bootstarp 工具类在启动前和启动后都监听了事件。只要有绑定对应的事件名的监听器,就会执行对应的监听器的 handle.


1.2 实例化 bootstarp 类,并且执行对应的 bootstrappers () 方法

我们选择 \Illuminate\Foundation\Bootstrap\HandleExceptions::class 来看


public function bootstrap(Application $app)

    {

        $this->app = $app;


        error_reporting(-1);


        set_error_handler([$this, 'handleError']);


        set_exception_handler([$this, 'handleException']);


        register_shutdown_function([$this, 'handleShutdown']);


        if (! $app->environment('testing')) {

            ini_set('display_errors', 'Off');

        }

    }

其实就是注册异常处理函数。


再看看门面的启动项 \Illuminate\Foundation\Bootstrap\RegisterFacades::class


 public function bootstrap(Application $app)

    {

        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance(array_merge(

            $app->make('config')->get('app.aliases', []),

            $app->make(PackageManifest::class)->aliases()

        ))->register();

    }

具体的功能实现可以去查看对应 Bootstrap 类的 bootstrap 方法的原理,这里先不展开讲。


2. 返回 $response 实例

终于到了获取 response 的方法了,其实前面这么多都是框架的启动阶段,这一步才是执行控制器逻辑的关键。


return (new Pipeline($this->app))

                    ->send($request)

                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)

                    ->then($this->dispatchToRouter());

这个就是 laravel 通过管道来实现中间件的调度。下一步我们解析一下 laravel 如何通过 request 访问到我们的控制器,然后返回 response 对象。博客:Laravel 从 $request 到 $response 的过程解析



  • 2017-07-05 09:48:51

    CSS 元素垂直居中的 6种方法

    利用CSS进行元素的水平居中,比较简单,行级元素设置其父元素的text-align center,块级元素设置其本身的left 和 right margins为auto即可。本文收集了六种利用css进行元素的垂直居中的方法,每一种适用于不同的情况,在实际的使用过程中选择某一种方法即可。

  • 2017-07-06 10:02:18

    大白话讲解Promise(一)

    去年6月份, ES2015正式发布(也就是ES6,ES6是它的乳名),其中Promise被列为正式规范。作为ES6中最重要的特性之一,我们有必要掌握并理解透彻。本文将由浅到深,讲解Promise的基本概念与使用方法。

  • 2017-07-11 21:54:14

    MYSQL5.7版本sql_mode=only_full_group_by问题

    一旦开启 only_full_group_by ,感觉,group by 将变成和 distinct 一样,只能获取受到其影响的字段信息,无法和其他未受其影响的字段共存,这样,group by 的功能将变得十分狭窄了

  • 2017-07-14 13:51:58

    NodeJS连接MySQL出现Cannot enqueue Handshake after invoking quit.

    原因在于node连接上mysql后如果因网络原因丢失连接或者用户手工关闭连接后,原有的连接挂掉,需要重新连接;如下代码,每次访问结束都关闭,每次开始访问前重连接下,代码中没有监听连接的fatal错误,copy需谨慎

  • 2017-07-14 13:53:02

    nodejs解决mysql和连接池(pool)自动断开问题

    最近在做一个个人项目,数据库尝试使用了mongodb、sqlite和mysql。分享一下关于mysql的连接池用法。项目部署于appfog,项目中我使用连接池链接数据库,本地测试一切正常。上线以后,经过几次请求两个数据接口总是报503。一直不明就里,今天经过一番排查终于顺利解决了。