Laravel ServiceProvider注册过程及简单使用
Laravel ServiceProvider注册过程及简单使用
还记得facade注册流程吗?回顾下
在bootstrap/app.php中返回$app实例后,通过singleton方法绑定了三个实现,然后将$app返回给了index.php,在index.php中尝试解析了http kernel,并且调用了kernel的handle方法(传递了请求实例),将通过handle方法返回的reponse返回给客户端,其中facade就是在处理请求的过程中注册的,同样的serviceprovider注册就在facade注册后面。
还记得这个数组吗?
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,
];
上篇讲解了RegisterFacades类
此文只讲解RegisterProviders类,至于BootProviders就留给大家自己追吧,其实就是调用服务提供者中可能存在的boot方法
- Illuminate\Foundation\Http\Kernel::handle方法中
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
// 跳转到bootstrap方法
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
// bootstrapWith传递的就是上面的数组
$this->app->bootstrapWith($this->bootstrappers());
}
}
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: ' . $bootstrapper, [$this]);
// 此文中$bootstrapper=\Illuminate\Foundation\Bootstrap\RegisterProviders
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: ' . $bootstrapper, [$this]);
}
}
// 以上代码上篇已经说过,此处再走一遍流程
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
// 跳转到registerConfiguredProviders方法,今天要说的都在这个方法里了
$app->registerConfiguredProviders();
}
public function registerConfiguredProviders()
{
// 访问了一个不存在的config属性,触发Container的__get方法 跳转一下
// Collection使用了EnumeratesValues trait,其中make方法返回Collection实例
// 我们携带app.providers数组跳转到Collection的构造方法
// 在此打印下Collection::make($this->config['app.providers'])
Illuminate\Support\Collection {#45 ▼
#items: array:27 [▼
0 => "Illuminate\Auth\AuthServiceProvider"
1 => "Illuminate\Broadcasting\BroadcastServiceProvider"
2 => "Illuminate\Bus\BusServiceProvider"
...
25 => "App\Providers\RouteServiceProvider"
26 => "App\Providers\MyFacadeProvider"
]
}
// 链式调用partition方法 跳转一下
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
// 在此打印$providers
Illuminate\Support\Collection {#32 ▼
#items: array:2 [▼
0 => Illuminate\Support\Collection {#43 ▼
#items: array:22 [▼
0 => "Illuminate\Auth\AuthServiceProvider"
...
21 => "Illuminate\View\ViewServiceProvider"
]
}
1 => Illuminate\Support\Collection {#31 ▼
#items: array:5 [▼
22 => "App\Providers\AppServiceProvider"
...
26 => "App\Providers\MyFacadeProvider"
]
}
]
}
// [$this->make(PackageManifest::class)->providers()此方法以数组的形式返回扩展包中的服务提供者
// 跳转到splice方法
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
dump($provider);
Illuminate\Support\Collection {#32 ▼
#items: array:3 [▼
0 => Illuminate\Support\Collection {#43 ▼
#items: array:22 [▼
0 => "Illuminate\Auth\AuthServiceProvider"
...
21 => "Illuminate\View\ViewServiceProvider"
]
}
1 => array:5 [▼
0 => "Facade\Ignition\IgnitionServiceProvider"
...
4 => "NunoMaduro\Collision\Adapters\Laravel\CollisionServiceProvider"
]
2 => Illuminate\Support\Collection {#31 ▼
#items: array:5 [▼
22 => "App\Providers\AppServiceProvider"
...
26 => "App\Providers\MyFacadeProvider"
]
}
]
}
// ProviderRepository 此类是真正注册service provider的地方
// $this->getCachedServicesPath() 默认返回的是/bootstrap/cache/services.php绝对路径
// 我们先看load方法中的参数,就是$provider属性中的所有数组元素,合并到一个大数组中并返回
// 跳转到ProviderRepository类的构造方法
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
public function __get($key)
{
// 上篇讲过Container实现了ArrayAccess接口
// 继续触发offsetGet方法 跳转一下
// 聪明的道友可能已经想到可不可以通过__set方法直接绑定实现到容器呢? 当然可以
// 有兴趣的可以自行查看__set方法
return $this[$key];
}
public function offsetGet($key)
{
// 到此解析出了config实例 跳转回registerConfiguredProviders方法
return $this->make($key);
}
Illuminate\Support\Collection文件
public function __construct($items = [])
{
$this->items = $this->getArrayableItems($items);
}
Illuminate\Support\Traits\EnumeratesValues文件
public function partition($key, $operator = null, $value = null)
{
$passed = [];
$failed = [];
// 调用partition方法的时候,只传递了一个闭包
$callback = func_num_args() === 1
// 自己追下代码,$callable就是传递进来的闭包
? $this->valueRetriever($key)
: $this->operatorForWhere(...func_get_args());
foreach ($this as $key => $item) {
// dump($key, $item);
// 在此将service provider进行了分组,分组规则就在闭包中
if ($callback($item, $key)) {
$passed[$key] = $item;
} else {
$failed[$key] = $item;
}
}
// 返回到registerConfiguredProviders方法并打印$providers
return new static([new static($passed), new static($failed)]);
}
Illuminate\Support\Collection文件
public function splice($offset, $length = null, $replacement = [])
{
if (func_num_args() === 1) {
return new static(array_splice($this->items, $offset));
}
// array_splice 第三个参数为0表示在offset后面添加数组元素
// 返回到registerConfiguredProviders方法并打印$providers
return new static(array_splice($this->items, $offset, $length, $replacement));
}
Illuminate\Foundation\ProviderRepository文件中
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
$this->app = $app;
$this->files = $files;
$this->manifestPath = $manifestPath;
}
/**
* Register the application service providers.
*
* @param array $providers
* @return void
*/
public function load(array $providers)
{
// 跳转到loadManifest方法
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
// 判断是否需要重新编译providers
// 比较$manifest 和 $providers
// 如果不同
if ($this->shouldRecompile($manifest, $providers)) {
// 若想进入此逻辑,可以手动删除bootstrap/cache下的services.php文件
// 让app重新缓存 跳转到compileManifest方法
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
// 注册事件 以后讲解
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
// dump($manifest); die;
foreach ($manifest['eager'] as $provider) {
// 如果不是延迟加载服务,那就在此阶段(也就是框架引导阶段直接调用服务的register方法)
// 跳转到app的register方法 前面文章也有讲到
$this->app->register($provider);
}
// 跳转到addDeferredServices方法,传递的数组都是要延迟加载的服务提供者
$this->app->addDeferredServices($manifest['deferred']);
// public function addDeferredServices(array $services)
// {
// $this->deferredServices = array_merge($this->deferredServices, $services);
// }
// 下面来看Application的make方法
}
public function loadManifest()
{
// The service manifest is a file containing a JSON representation of every
// service provided by the application and whether its provider is using
// deferred loading or should be eagerly loaded on each request to us.
// load方法会判断文件是否存在
// 如果/bootstrap/cache/services.php存在就加载其中的数组
if ($this->files->exists($this->manifestPath)) {
$manifest = $this->files->getRequire($this->manifestPath);
// dump($manifest);
array:4 [▼
"providers" => array:32 [▼
0 => "Illuminate\Auth\AuthServiceProvider"
...
]
"eager" => array:19 [▼
0 => "Illuminate\Auth\AuthServiceProvider"
...
]
"deferred" => array:105 [▼
"Illuminate\Broadcasting\BroadcastManager" => "Illuminate\Broadcasting\BroadcastServiceProvider"
...
]
"when" => array:13 [▼
"Illuminate\Broadcasting\BroadcastServiceProvider" => []
...
]
]
if ($manifest) {
// 返回到load方法
return array_merge(['when' => []], $manifest);
}
}
}
// 比较文件缓存和传递的$providers是否相等 跳转回load方法
public function shouldRecompile($manifest, $providers)
{
// dump($manifest, $providers);
return is_null($manifest) || $manifest['providers'] != $providers;
}
protected function compileManifest($providers)
{
// freshManifest方法返回数组 因为需要重新编译provider所以将when删除了
// ['providers' => $providers, 'eager' => [], 'deferred' => []];
$manifest = $this->freshManifest($providers);
// 此时的$providers就是除了框架实例化时就注册的全部服务提供者了
foreach ($providers as $provider) {
// createProvider方法 return new $provider($this->app);
$instance = $this->createProvider($provider);
// When recompiling the service manifest, we will spin through each of the
// providers and check if it's a deferred provider or not. If so we'll
// add it's provided services to the manifest and note the provider.
// 我们所有的Provider都继承自ServiceProvider基类
// 如果实现了DeferrableProvider的provides接口就认为此服务要延迟加载
if ($instance->isDeferred()) {
// 判断一个服务是否延迟加载 需要实现Illuminate\Contracts\Support\DeferrableProvider接口的provides方法 返回一个数组
foreach ($instance->provides() as $service) {
// 老版本的laravel 如果想表明一个延迟服务 需要在对应的provider中 加上protected $defer = true属性
// 现版本需要在provider中实现DeferrableProvider接口
// 查看 Illuminate\Redis\RedisServiceProvider 其中的provides方法
// public function provides()
// {
// return ['redis', 'redis.connection'];
// }
$manifest['deferred'][$service] = $provider;
}
// 如果provider没有实现when方法 基类返回[]
$manifest['when'][$provider] = $instance->when();
}
// If the service providers are not deferred, we will simply add it to an
// array of eagerly loaded providers that will get registered on every
// request to this application instead of "lazy" loading every time.
else {
// 如果不是一个延迟服务 那么直接将服务放到eager数组中
$manifest['eager'][] = $provider;
}
}
// 将服务提供者写入文件 跳转回load方法
return $this->writeManifest($manifest);
}
Illuminate\Foundation\Application文件
public function register($provider, $force = false)
{
if (($registered = $this->getProvider($provider)) && !$force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
// 可以看到register方法 是官方更加推荐的注册服务提供者的方式
if (is_string($provider)) {
// new一个provider
$provider = $this->resolveProvider($provider);
}
// 调用服务提供者中的register方法
$provider->register();
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
// 按道理来说每个serviceprovider都可以拥有这两个属性
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 the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
// 如果框架已经boot完毕 那么给后注册的provider执行boot方法
if ($this->isBooted()) {
// 还记得protected $booted属性在哪里设置为true的吗
// 就在开篇的bootstrapWith方法中 表示框架已经引导完毕
// 执行provider中的boot方法
$this->bootProvider($provider);
}
// 跳转回load方法
return $provider;
}
/**
* Resolve the given type from the container.
*
* (Overriding Container::make)
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
// 毫无疑问 重写了Container的make方法
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($abstract);
if ($this->isDeferredService($abstract) && !isset($this->instances[$abstract])) {
// 跳转吧
$this->loadDeferredProvider($abstract);
}
return parent::make($abstract, $parameters);
}
public function loadDeferredProvider($service)
{
if (!$this->isDeferredService($service)) {
return;
}
$provider = $this->deferredServices[$service];
// If the service provider has not already been loaded and registered we can
// register it with the application and remove the service from this list
// of deferred services, since it will already be loaded on subsequent.
if (!isset($this->loadedProviders[$provider])) {
// 跳转吧
$this->registerDeferredProvider($provider, $service);
}
}
public function registerDeferredProvider($provider, $service = null)
{
// Once the provider that provides the deferred service has been registered we
// will remove it from our local list of the deferred services with related
// providers so that this container does not try to resolve it out again.
if ($service) {
unset($this->deferredServices[$service]);
}
// 可以看到最终还是调用了register方法 完结!!!
$this->register($instance = new $provider($this));
if (!$this->isBooted()) {
$this->booting(function () use ($instance) {
$this->bootProvider($instance);
});
}
}
简单测试
Route::get('redis', function(){
dump(resolve('app')->getDeferredServices());
// 使用延迟解析
dump(app()->app->getLoadedProviders()); // 没有redis对应的RedisServiceProvider
dump(get_class(resolve('redis')));
dump(app()['app']->getLoadedProviders()); // 有redis对应的ServiceProvider
});
到此服务注册算是完结了。
下集预告: 传递到handle方法中的$request到底是啥及laravel中的管道。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步