标签: laravel 源码分析
在我们学习一个框架的过程中,了解一个框架的启动流程,对于我们理解、使用好框架具有很大帮助,今天我们就来看一下 laravel 框架启动过程。
框架启动过程中的相关类
在 laravel 启动过程中,主要涉及到以下类:
Illuminate\Foundation\Application
Application 是 laravel 框架最核心的类之一。它首先是一个 IOC 容器,管理整个框架类对象的定义、实例化、存储;同时它也是整个框架的应用类,管理应用中的 ServiceProvider,启动、停止应用,启动针对不同应用的启动器。App\Http\Kernel
针对 http 请求的核心类,继承自Illuminate\Foundation\Http\Kernel
,管理框架中的路由,配置针对 http 应用的启动器,执行 http 请求,停止针对 http 请求的应用。Illuminate\Routing\Router
框架路由系统的门面类,管理并配置路由,将请求分发到路由并返回路由执行请求的响应。Illuminate\Routing\Route
框架路由系统的路由类,每个配置的路由对应类的一个实例,里面记录了匹配此路由请求的 uri、methods 等信息,以及对应的执行请求的控制器方法或者回调等信息,实现了判断一个请求是否匹配此路由的方法。Illuminate\Http\Request
http 请求类,主要作用是封装 http 请求的query、server、input、 file、cookie、session 等参数。Illuminate\Http\Response
http 响应类,构造框架的 http 响应,里面包括响应码、响应内容、响应头等信息。实现了发送响应的方法。
框架启动过程源码分析
接下来,我们来看框架的启动过程。我们知道,针对一个 web 应用,其入口文件是在 /public/index.php
,我们来看这个文件具体内容。
//注册应用的自动加载器
require __DIR__.'/../bootstrap/autoload.php';
//创建 Application 类对象,做初始配置并返回
$app = require_once __DIR__.'/../bootstrap/app.php';
//创建针对 http 请求的 $kernel 对象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//在 $kernel 中执行请求 $request,并得到响应 $response
//$response 是 Illuminate\Http\Response 类的实例
$response = $kernel->handle(
//根据 http 请求创建 $request 对象
//其为 Illuminate\Http\Request 对象实例
//管理 http 请求的query, server, input, file, cookie, session等信息
$request = Illuminate\Http\Request::capture()
);
//发送响应
$response->send();
//终止针对请求 $request 得到响应 $response 的 $kernel
$kernel->terminate($request, $response);
我们看了应用入口文件的内容,了解了框架的大概启动流程,接下来我们来分析各个流程的具体内容。
框架的自动加载文件
在在入口文件中,我们看到,代码首先加载了框架的自动加载配置文件,我们来看其具体内容。
define('LARAVEL_START', microtime(true));
//加载由 composer 生成的框架各个包的自动加载规则和我们自定义的自动加载规则
//php 包的自动加载规则主要遵循 psr-4 标准
require __DIR__.'/../vendor/autoload.php';
$compiledPath = __DIR__.'/cache/compiled.php';
if (file_exists($compiledPath)) {
require $compiledPath;
}
框架 Application 类对象的实例化及配置
当框架加载完自动加载文件后,开始生成核心类 Application 并做基本配置,我们来看具体代码。
//创建 Application 类对象
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
//添加绑定针对 http 请求的 Kernel 的绑定
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
我们来看 Application 的构造函数,看一下 Application 类对象的实例化过程
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
//在 Application 的容器里面添加基础的 binding
//主要是 app 和 Illuminate\Container\Container
$this->registerBaseBindings();
//注册基础的 service provider
//主要是启动事件监听服务和路由服务的 service provider
$this->registerBaseServiceProviders();
//为框架里面的核心类注册别名
$this->registerCoreContainerAliases();
if ($basePath) {
//设置应用的基础路径,并将基于基础路径得到的常用的其他路径绑定到容器
$this->setBasePath($basePath);
}
}
}
Kernel 对象实例化及基本功能
在入口文件中,我们看到实例化 Application 类对象后,就开始了创建 Kernel 对象,创建请求的 $request
对象,并在 Kernel 对象中执行请求 $request
,得到相应 $response
。如下代码所示:
//创建针对 http 请求的 $kernel 对象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//在 $kernel 中执行请求 $request,并得到响应 $response
//$response 是 Illuminate\Http\Response 类的实例
$response = $kernel->handle(
//根据 http 请求创建 $request 对象
//其为 Illuminate\Http\Request 对象实例
//管理 http 请求的query, server, input, file, cookie, session等信息
$request = Illuminate\Http\Request::capture()
);
实例化 Kernel 对象
我们先来看 Kernel 对象的实例化过程
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The router instance.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* The application's global HTTP middleware stack.
* 运行 http 请求全局生效的中间件
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [];
/**
* The application's route middleware groups.
* 应用中路由的中间件组
* @var array
*/
protected $middlewareGroups = [];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
* 路由中可能会用到的中间件的简化别名
* @var array
*/
protected $routeMiddleware = [];
/**
* The priority-sorted list of middleware.
* Forces the listed middleware to always be in the given order.
* 中间件的优先级,强制下面这些中间件按照给定的顺序执行
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
/**
* Create a new HTTP kernel instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
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->middleware($key, $middleware);
}
}
}
我们看到在实例化 Kernel 对象过程中,主要设置了路由系统的中间件优先级、中间件别名和中间件组。
Kernel 对象执行请求过程
接下来我们来看在 $kernel
对象中执行请求 $request
并返回相应 $response
的过程。
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The router instance.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* Handle an incoming HTTP request.
* 执行一个 http 请求
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\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']->fire('kernel.handled', [$request, $response]);
return $response;
}
/**
* Send the given request through the middleware / router.
* 发送 $request,通过全局中间件,并分发给路由
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
//在 Application 类对象上绑定请求类 $request
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
//根据这个 http 请求启动应用,运行针对 http 请求的启动器
$this->bootstrap();
//将请求 $request 通过中间件,并分发给路由
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
/**
* Get the route dispatcher callback.
* 返回在路由上分发请求的闭包
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
//根据路由分发请求
return $this->router->dispatch($request);
};
}
}
我们看到将请求 $response
分发到路由并执行返回其响应调用了路由系统的相关方法,我们来看具体实现。
路由系统分发并执行请求
在 laravel 框架中,Illuminate\Routing\Router
类主要的作用是配置应用的路由,配置路由可能用到的中间件、中间件组,根据配置的路由表分配请求,根据分配到的路由运行请求得到相应。下面我们来看 Illuminate\Routing\Router
类中和框架启动相关的源码
namespace Illuminate\Routing;
class Router implements RegistrarContract
{
/**
* Dispatch the request to the application.
* 分发请求到某个配置的路由
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
* 分发请求到某个路由,并返回执行请求得到的响应
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
//首先,在用户配置的路由组里面找到匹配请求的路由
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
//触发 RouteMatched 事件
$this->events->fire(new Events\RouteMatched($route, $request));
//让 $request 通过 $route 配置的中间件,得到运行路由的响应
$response = $this->runRouteWithinStack($route, $request);
//根据 $request 和 $response,准备 $response 响应对象
return $this->prepareResponse($request, $response);
}
/**
* 运行路由的中间件,让 $request 通过 $route 配置的中间件
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
//是否应该跳过中间件
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
//让请求 $request 通过 $route 的中间件,
//并最终返回路由执行请求的响应
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run($request)
);
});
}
/**
* Create a response instance from the given value.
* 根据运行路由得到的响应配置框架的响应
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Illuminate\Http\Response
*/
public function prepareResponse($request, $response)
{
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
return $response->prepare($request);
}
}
laravel 框架路由系统中,类 Illuminate\Routing\Route
也是重要的核心类之一,在框架中,每配置一个请求的路由,都会生成一个 Illuminate\Routing\Route
类的实例,里面记录类请求的 uri、methods,以及对应的执行请求的控制器方法或者回调等信息。接下来我们来看类 Illuminate\Routing\Route
中和框架启动相关的内容。
namespace Illuminate\Routing;
class Route
{
/**
* Run the route action and return the response.
* 运行路由配置的 action 并返回其响应
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function run(Request $request)
{
$this->container = $this->container ?: new Container;
try {
//如果路由是有控制器的方法执行
//运行控制器对应的方法并返回
if ($this->isControllerAction()) {
return $this->runController();
}
//运行并返回路由对应的回调
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
}
http应用的启动
在 Kernel 对象执行请求过程中,我们看到在将请求分发到路由之前,我们先启动了针对这个 http 请求的应用(通过语句 $this->bootstrap()
实现,$this
表示对象 $kernel
)。接下来我们来看应用的启动过程。
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The bootstrap classes for the application.
* 启动一个 http 请求的应用所需的启动器的类
* @var array
*/
protected $bootstrappers = [
'Illuminate\Foundation\Bootstrap\DetectEnvironment',
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
'Illuminate\Foundation\Bootstrap\ConfigureLogging',
'Illuminate\Foundation\Bootstrap\HandleExceptions',
'Illuminate\Foundation\Bootstrap\RegisterFacades',
'Illuminate\Foundation\Bootstrap\RegisterProviders',
'Illuminate\Foundation\Bootstrap\BootProviders',
];
/**
* Bootstrap the application for HTTP requests.
* 根据这个 http 请求启动应用,运行针对 http 请求的启动器
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
/**
* Get the bootstrap classes for the application.
* 返回针对 http 请求的启动器类
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}
}
我看再来看 Application 类中启动一组启动器的相关代码
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Run the given array of bootstrap classes.
* 启动 $bootstrappers 数组里面的启动器
* @param array $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
//触发启动器 $bootstrapper 启动的监听事件
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
//触发启动器 $bootstrapper 启动后的监听事件
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
}
根据以上代码,我们知道所谓启动针对 http 请求的应用,也就是依次运行在 $kernel
对象中配置的启动器,每个启动器实现了启动应用过程中的某一块功能。
各个启动器具体功能如下:
Illuminate\Foundation\Bootstrap\DetectEnvironment
检测并配置环境。当框架没有缓存配置的时候,加载 .env 文件里面的配置信息Illuminate\Foundation\Bootstrap\LoadConfiguration
加载框架的配置信息。当配置文件已经缓存,从配置缓存文件中加载配置信息,否则依次加载框架的配置文件里面的配置信息Illuminate\Foundation\Bootstrap\ConfigureLogging
注册、配置并启动框架的日志系统Illuminate\Foundation\Bootstrap\HandleExceptions
设置框架运行时的异常报错机制Illuminate\Foundation\Bootstrap\RegisterFacades
注册框架的门面Illuminate\Foundation\Bootstrap\RegisterProviders
注册框架的 service provider,也就是运行非延迟执行的 ServiceProvider 的register
方法(执行$app->registerConfiguredProviders()
语句实现)Illuminate\Foundation\Bootstrap\BootProviders
启动框架的 service provider,也就是运行非延迟执行的 ServiceProvider 的boot
方法(通过执行$app->boot()
语句实现)
由于框架是依次执行各个启动器,所以我们知道了为什么 laravel 框架中, ServiceProvider 先执行 register
方法,后执行 boot
方法。
终止应用相关源码
在将请求分发到路由并执行得到相应之后,开始发送响应并在 Kernel 类对象中终止应用:
//发送响应内容
$response->send();
//终止针对请求 $request 得到响应 $response 的 $kernel
$kernel->terminate($request, $response);
我们看到终止应用是通过 $kernel
对象调用 terminate
方法得到的,我们来看相应源码
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* Call the terminate method on any terminable middleware.
* 停止这个针对 http 请求的应用
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
//获取请求经过的中间件
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
//运行各个中间件的 terminate 方法
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
list($name, $parameters) = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
//终止应用,应用 Application 对象的 terminate 方法
$this->app->terminate();
}
}
在 $kernel
终止的过程中,调用了 Application 类对象的 terminate
方法,我们来看一下其相关方法
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Boot the application's service providers.
* 启动这个 application 的 service provider
* @return void
*/
public function boot()
{
//如果应用已经启动,则直接返回
if ($this->booted) {
return;
}
//触发应用的启动的回调函数
$this->fireAppCallbacks($this->bootingCallbacks);
//启动框架中的设置的 service provider
//array_walk 函数对数组中的每个元素应用用户自定义函数
//在函数中,第一个参数是值,第二个参数是键。
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
//设置应用已经启动
$this->booted = true;
//触发应用启动后的回调函数
$this->fireAppCallbacks($this->bootedCallbacks);
}
/**
* Terminate the application.
* 终止这个应用
* @return void
*/
public function terminate()
{
//运行应用结束时的回调
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}
}
通过以上的代码分析,我们可以看到 Application 类对象的 boot
方法是通过 Kernel 对象配置的启动器 Illuminate\Foundation\Bootstrap\BootProviders
来调用的,而 terminate
方法则是通过 Kernel 类对象的 terminate
方法来调用的。
总结
至此,我们大概了解了 laravel 框架的启动过程。了解框架的启动执行过程对于我们调试代码、理解框架设计思想具有很大帮助。
不过,框架的设计是一个整体性的东西,我们不仅要了解框架的启动过程,还要知道其他模块的运行原理,比如 Application 对 ServiceProvider 的管理,框架中间件的实现过程,Facade 的设计思想及实现,这些都是框架里面的核心概念和内容,理解这些东西,对于我们更好的理解和使用框架具有很大的帮助。大家可以看我相关的文章,通过这些文章的阅读,相信对理解框架启动过程和整体设计思想具有很大帮助。