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

本作品采用《CC 协议》,转载必须注明作者和本文链接
用过哪些工具?为啥用这个工具(速度快,支持高并发...)?底层如何实现的?
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 1

分析的不错。 不知是否有什么办法可以生成流程图或者 脑图类。 看起来跟清晰。

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!