Laravel 启动流程分析 (代码全流程)
Laravel 启动流程分析
入口文件 index.php
中,引入核心启动文件
$app = require_once __DIR__.'/../bootstrap/app.php';
app.php
做了什么事情?
// 实例化应用 $app
$app = new Illuminate\Foundation\Application(
dirname(__DIR__)
);
接下来全程跟踪
$app
中属性的变化
看下 Application
中的构造方法,逐行通过注释分析
// 字面理解
if ($basePath) {
// 设置基础路径信息
$this->setBasePath($basePath);
}
// 注册基础绑定
$this->registerBaseBindings();
// 注册基础服务容器
$this->registerBaseServiceProviders();
// 注册核心容器别名
$this->registerCoreContainerAliases();
快速看代码 setBasePath
方法代码
* * * *
$this->bindPathsInContainer();
* * * *
快速看 bindPathsInContainer
代码
// 绑定非自动加载路径到容器
* * * *
$this->instance('path', $this->path());
* * * *
$app->instances = [
'path' => 'xxx'
]
认真看 registerBaseBindings
代码
// 绑定 Application 实例到 @todo Container::$instance
static::setInstance($this);
// 绑定 Application 实例到 $app->instances['app']
$this->instance('app', $this);
// 绑定 Application 实例到 $app->instances['Container'] 数组
$this->instance(Container::class, $this);
// 绑定 PackageManifest 实例到 $app->instances['PackageManifest'] 数组
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
* * * *
$app->instances['app'] = $this
$app->instances['Container'] = $this
$app->instances['PackageManifest'] = new PackageManifest(xxx)
注意 $this 是 Application 实例
仔细看 registerBaseServiceProviders
代码
// 只举例分析第一条,一下两行代码类似
$this->register(new EventServiceProvider($this));
// 注册日志容器
$this->register(new LogServiceProvider($this));
// 注册路由容器
$this->register(new RoutingServiceProvider($this));
先看参数,每个resister的参数都是一个 ServiceProvider 实例,简单看一下 EventServiceProvider 类 和 其继承的 ServiceProvider 抽象类
abstract class ServiceProvider
{
* * * *
protected $app;
public function __construct($app)
{
// 将 Application 实例传给 app 属性
$this->app = $app;
}
* * * *
}
关键源码分析 敲黑板 关键
* * * *
register源码
public function register($provider, $force = false)
{
// 检查容器是否注册过,如果已经注册过,直接返回容器
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// 如果传入的是字符串,则通过字符串解析容器
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 由于传入的是一个Provider实例,并且含有 register 方法
if (method_exists($provider, 'register')) {
$provider->register();
}
// 如果传入的是Provider实例,并且含有 bindings 方法
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 如果传入的是Provider实例,并且含有 singletons 方法
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
// 将Provider打上已经注册的标识
$this->markAsRegistered($provider);
// 如果应用已经启动,则调用Provider实例的 boot 方法
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
关于服务容器中 register
,bindings
,singletons
,boot
方法的使用场景,可以查阅 这里
这里举例看EventServiceProvider的register方法查看
class EventServiceProvider extends ServiceProvider
{
public function register()
{
// 调用了 Application 实例的 singleton 方法
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
}
* * * *
查看 Application 实例的 singleton 方法
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
查看 Application 实例的 bind 方法
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
// 传入的 concrete 是一个回调函数
if (! $concrete instanceof Closure) { // false
$concrete = $this->getClosure($abstract, $concrete);
}
// $this->bindings['events'] = [concrete (这是前面传入的闭包函数), true (是否共享,也就是是否单例)];
$this->bindings[$abstract] = compact('concrete', 'shared');
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
到此,singleton执行完毕,我们可以看到最终的结果,是把一个闭包函数
function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
}
和 key events
以键值对的方式传入了 $app 下的 bindings 属性
$app->bindings['events'] = [
function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
},
true
]
由此,我们在回头看一下我们的 Application 类的属性组成 tips: 主要是 Container 基类
/**
* The current globally available container (if any).
* 当前全局可用容器
* @var static
*/
protected static $instance;
/**
* An array of the types that have been resolved.
* 一个已经被解析的类型数组
* @var array
*/
protected $resolved = [];
/**
* The container's bindings.
* 容器的绑定数组
* @var array
*/
protected $bindings = [];
/**
* The container's method bindings.
* 容器的方法绑定数组
* @var array
*/
protected $methodBindings = [];
/**
* The container's shared instances.
* 容器的可被分享的实例数组
* @var array
*/
protected $instances = [];
我们经过上面的分析,我们可以得到如下结果
$app->instances = [x,x,x]
$app->bindings = [x,x,x]
$app->resolved = [x,x,x]
我们是不是可以得出一个大概的推论,Laravel的启动过程,其实是以上这些属性填满的过程,
换句话说,这些属性数组保存了laravel启动过程中基本所有需要使用到的东西 :)
OK,回到 Application 的构造方法继续分析,剩下
$this->registerCoreContainerAliases();
* * * *
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
. . . .
] as $key => $aliases) {
foreach ($aliases as $alias) {
// 将核心容器起别名放入 $app->alias 属性中
$this->alias($key, $alias);
}
}
* * * *
$app->alias = [
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class]
. . . .
]
$app->alias
被赋值是不是从侧面也印证了上面的推论 :)
经过上面一大串子分析,Laravel已经加载了大部分的基础服务,现在是不是还有个疑问,我们的请求是怎么到Controller的呢
答案就在app.php的下面的几行代码了
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
OK,我们在看一遍 singleton 的源码 (精简版的 :) )
// 传入的 ¥concrete 是 App\Http\Kernel::class 是一串字符串,并不是闭包
if (! $concrete instanceof Closure) {
// 执行 getClosure 函数
$concrete = $this->getClosure($abstract, $concrete);
}
// 老样子,放到 bindings 数组中,作为单例存在
$this->bindings[$abstract] = compact('concrete', 'shared');
* * * *
// 接着看 getClosure
// 函数返回了一个闭包
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
// 这里 $abstract != $concrete
if ($abstract == $concrete) {
return $container->build($concrete);
}
// 执行 Application 实例的make方法,传入 App\Http\Kernel::class, []
return $container->make($concrete, $parameters);
};
}
* * * *
// 接着看 Application实例的make方法
public function make($abstract, array $parameters = [])
{
// mmp 调用了 resolve
return $this->resolve($abstract, $parameters);
}
* * * *
// 传入 App\Http\Kernel::class 和 []
protected function resolve($abstract, $parameters = [])
{
// 没有别名
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// 没有实例化过
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
// $concrete === $abstract 为 true
if ($this->isBuildable($concrete, $abstract)) {
// 进入这里
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// 添加扩展,暂时忽略
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// 如果是单例,假如instances数组
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
// 假如已解析数组
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
* * * *
// 查看build方法,快结束了,撑住
public function build($concrete)
{
// 如果是闭包,执行闭包
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
// 实例化反射类,传入 App\Http\Kernel::class
$reflector = new ReflectionClass($concrete);
// 如果不能被实例化,报错
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
// buildStack是一个需要被实例化的类组成的栈
$this->buildStack[] = $concrete;
// 获取构造方法
$constructor = $reflector->getConstructor();
// 如果没有构造方法,则直接实例化这个类
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 否则就获取构造方法的参数,参数即为该类的依赖
$dependencies = $constructor->getParameters();
// 递归解析并实例化依赖
$instances = $this->resolveDependencies(
$dependencies
);
// 弹出栈 note: 注意递归的情况
array_pop($this->buildStack);
// 传入依赖并实例化类 note:注意递归
return $reflector->newInstanceArgs($instances);
}
通过上面的code,我们已经实例化的 App\Http\Kernel::class 类,并且已经解决了类的依赖问题
需要特别注意的一点是,该类在的构造函数中还做了一些别的操作,比如中间件的初始化等操作。
还是贴代码看一下吧
// App\Http\Kernel::class 类依赖 Application 和 Router类
// 通过反射机制,解决了依赖问题
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);
}
}
Ok,HttpKernel部分分析完成。
以下两段代码分析与上述过程一样,不在赘述
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
再回到梦开始的地方 index.php
,接着分析
// 通过Application的make方法,可以直接拿出Kernel实例,因为之前已经做过单例了
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 调用 Kernel 实例的 handle 方法
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
* * * *
// 接着看 Illuminate\Http\Request::capture() 静态方法
// 这里只贴往下追后的代码了昂
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
$content = $request->content;
$request = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$request->content = $content;
$request->request = $request->getInputSource();
return $request;
}
// 以上这段代码的核心就是构造了一个 Request 对象,没什么特别的
* * * *
// 接着看 handle 方法,只贴一行代码
$response = $this->sendRequestThroughRouter($request);
// 哇塞,好激动有没有,看到这行代码的我差点哭出来,请求和路由有关系了
// 继续往下看 sendRequestThroughRouter 方法
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
// 看这里,dispatchToRouter 将请求分发到路由上哎
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
* * * *
// 继续往下坠 dispatchToRouter ,会有一个方法
protected function findRoute($request)
{
// 这里的routes是一个 RouteCollection 类,通过match方法,将请求解析到具体的路由上
// RouteCollection是什么时候创建的???兄弟,还记得之前的 RoutingServiceProvider吗 :),在注册的时候都已经跑完了
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
至此,O了个K,Laravel的启动流程结束。
Life is fantastic!!!
本作品采用《CC 协议》,转载必须注明作者和本文链接
学习了
@lovecn 共勉 :smile:
解开我多年的困惑
666