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中的管道。