Laravel 的啟動流程

bytecc發表於2020-01-19

很多人說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 協議》,轉載必須註明作者和本文連結
用過哪些工具?為啥用這個工具(速度快,支援高併發...)?底層如何實現的?

相關文章