很多人說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 協議》,轉載必須註明作者和本文連結