参考地址 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 的过程解析