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 的过程解析



  • 2020-03-13 19:58:19

    推荐Android两种屏幕适配方案

    在Android开发中,由于Android碎片化严重,屏幕分辨率千奇百怪,而想要在各种分辨率的设备上显示基本一致的效果,适配成本越来越高。虽然Android官方提供了dp单位来适配,但其在各种奇怪分辨率下表现却不尽如人意,因此下面探索一种简单且低侵入的适配方式。本文将推荐两种屏幕适配方案,大家可以根据实际情况使用。

  • 2020-03-14 16:35:00

    nuxt.js部署全过程(ubuntu+nginx+node+pm2)

    系统的话本篇是Ubuntu 16.04.6 ,centos也行,大同小异都是Linux。不过如果你是初学者,最好和我使用一样的,因为因为发行版本不同而导致的差异可能导致运行某些东西失败,找问题要找好久。windows server不推荐了,企业用的多,小服务器跑windows server比较费劲。

  • 2020-03-14 23:15:25

    icomoon使用详细介绍

    此篇博文讲述如何利用icomoon导入图标,从而把自己想要的都通过icomoon方式进行,大家都知道,网站以及移动端,用图标还是尽量选择这种。因为直接用image有些图标会失真,从而也是前端开发之中,需求去掌握的一项,很简单的就几个步骤。

  • 2020-03-14 23:39:59

    vuetify和@nuxt/vuetify icon 之我见

    vuetify中v-icon,貌似默认支持 Material Design Icons, Material Icons, Font Awesome 4 and Font Awesome 5, 我自己单独引入了vuetify 用哪一个图标都没有问题。但是用了@nuxt/vuetify只能用mdi-home这样的。不知道因为啥。肯定是封装后,封装成一个了。 但是我修改vuetify的设置,哪一个图标也都能用。哎,不过多研究了。

  • 2020-03-16 15:57:53

    nuxtjs中单独引入Message组件的问题

    // 引入elementUIimport { Message } from 'element-ui';//由于Message组件并没有install 方法供Vue来操作的,是直接返回的,因此按照官方文档单独引入的方法是//会报错的,需要给 Message 添加 install 方法Message.install = function (Vue, options) {Vue.prototype.$message = Message}Vue.use(Message )//消息提示

  • 2020-03-16 16:03:20

    css的var()函数

     随着sass,less预编译的流行,css也随即推出了变量定义var函数。var()函数,就如同sass和less等预编译软件一样,可以定义变量并且进行对应的使用。

  • 2020-03-16 16:52:05

    对icomoon的误解,以及最快速的使用

    此时需要注意顶部第一个选项,Quick Usage,一定要打开,Enable Quick Usage,谁让咱英语不好呢,这个时候会出现一个css连接,直接引用就好了,就可以随意使用图标了,引入这一个css就能实现我们的功能,省区引入太多文件的烦恼,你可以在浏览器打开这个css,可以看到里面把我们所用的文件整成base64了。所以挺好用的。