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-18 21:15:34

    使用canvas画布解决百度地图自定义图层全球连续显示问题

        基于百度地图的Web API进行自定义图层叠加时,默认的图层只能叠加到全球范围以内,即经度范围为[-180, 180],而无法将图层叠加到默认的全球范围以外,即经度范围超出了[-180, 180]之后,经纬度坐标会自动回归到(0, 0),而导致在地图拖拽时全球以外无法连续显示想要的图层,此时可以基于百度地图的自定义图层将经纬度坐标转为像素点使用画布canvas来解决该问题。解决后效果如下图所示: ———————————————— 版权声明:本文为CSDN博主「宏伟杰作」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u011284073/article/details/80549950

  • 2020-03-18 21:18:01

    node-canvas实现百度地图个性化底图绘制

    随着nodejs的推出,node的并发和异步的强大能力,越来越多的得到应用,而且取得了非常不错的效果。 作为一个前端工程师对node.js自然有着一份更深的感情,跃跃欲试的心情,总希望能将它应用到产品中来。

  • 2020-03-18 21:19:28

    高德地图和canvas画图结合应用的一些感想(一)

    入了团队才发现,该项目前后端分离,后端工程师已就位主要实现接口,IOS端工程师也已就位,还差一个web前端工程师。背脊一凉,我之前虽然写过一些js和css,虽有点功底但是离前端工程师还是有距离的啊。在和朋友说明情况后,朋友也是胆大,让我试试,主要他实在找不到人了(也有可能目前前端工程师报价都太贵了,创业嘛,能节约就节约,能理解。。。),没办法,走一步算一步吧。

  • 2020-03-18 21:30:57

    基于OpenLayers实现地理围栏 谷歌百度高德地图都可以

    前言.因为项目有点特殊,需要接入谷歌地图实现地理围栏,因为谷歌地图的地理围栏接口相关接口并不完善,于是就换了一个思路,利用OpenLayers来实现地理围栏 openlayers 中文地址 http://weilin.me/ol3-primer/ch02/index.html 作者:zcty0701 链接:https://www.jianshu.com/p/60e88ee1e843 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。