Application 繼承了 Container,同時也是整個 Laravel 生命週期會用到的共同容器。而 Laravel 為了做到元件可獨立使用,所以大部分的元件,為了要取得其他相依元件,都會只依賴 Container。
因此 Application 必須要遵守里氏替換原則,才不會有意外發生。
可以翻了一下原始碼,有下列方法被覆寫:
public function bound($abstract) { return isset($this->deferredServices[$abstract]) || parent::bound($abstract); }
public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($abstract);
if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) { $this->loadDeferredProvider($abstract); }
return parent::make($abstract, $parameters); }
public function flush() { parent::flush();
$this->buildStack = []; $this->loadedProviders = []; $this->bootedCallbacks = []; $this->bootingCallbacks = []; $this->deferredServices = []; $this->reboundCallbacks = []; $this->serviceProviders = []; $this->resolvingCallbacks = []; $this->afterResolvingCallbacks = []; $this->globalResolvingCallbacks = []; }
|
可以思考一下這些方法被覆寫時,是如何避免破壞原有的行為。比方說,要覆寫改變物件狀態的方法,通常都會有明確呼叫父類別的方法(parent::method()
)來確保原有的行為依然會被執行。像 flush()
就很好理解,它先把原本 Container 的狀態清除,再把 Application 的狀態清除。
建構子
與 Container 不同,Application 是有建構子的:
public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); }
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases(); }
|
其中特別提一下預設的 service provider,也就是一開始 Application 會準備好哪些 service。
protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this)); }
|
所以這幾個 service provider 沒在 config/app.php
裡面出現,但莫名奇妙的它們能 work 的原因就在這裡。
Register Service Provider
register()
的註冊邏輯分析如下:
public function register($provider, $force = false) { if (($registered = $this->getProvider($provider)) && ! $force) { return $registered; }
if (is_string($provider)) { $provider = $this->resolveProvider($provider); }
if (method_exists($provider, 'register')) { $provider->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()
邏輯是比較單純的,複雜的其實是從 bootstrap 流程如何進到這裡。第二天曾提到,bootstrapWith()
載了很多 bootstrappers,其中有一個是 RegisterProviders
,這正是註冊所有 service provider 的起始點。
public function bootstrap(Application $app) { $app->registerConfiguredProviders(); }
|
而它其實把註冊邏輯全寫到 Application::registerConfiguredProviders()
了,這裡就不是很好理解了。
$providers = Collection::make($this->config['app.providers']) ->partition(function ($provider) { return Str::startsWith($provider, 'Illuminate\\'); });
|
首先把 config/app.php 裡面的 providers 拆成兩組 array:Illuminate 自家的和開發者自己寫在設定的。
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
|
PackageManifest
是 Laravel 5.5 推出的新功能--Package Discovery 的實作。
接著把 PackageManifest
所解析出來的 providers 插入在中間,排序就會變成:
- Illuminate
- PackageManifest
- Custom
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray());
|
最後使用 ProviderRepository::load()
來將所有 provider 都載入。我們來看看裡面做些什麼,因為裡面有 Application 的另外一個重要功能。
public function load(array $providers) { $manifest = $this->loadManifest();
if ($this->shouldRecompile($manifest, $providers)) { $manifest = $this->compileManifest($providers); }
foreach ($manifest['when'] as $provider => $events) { $this->registerLoadEvents($provider, $events); }
foreach ($manifest['eager'] as $provider) { $this->app->register($provider); }
$this->app->addDeferredServices($manifest['deferred']); }
|
是的,Application 另外一個重要的功能就是 lazy loading,這也是原本的 Container 沒有的。
再來看一下 compileManifest()
到底幫我們產生什麼樣的資料:
protected function compileManifest($providers) { $manifest = $this->freshManifest($providers);
foreach ($providers as $provider) { $instance = $this->createProvider($provider);
if ($instance->isDeferred()) { foreach ($instance->provides() as $service) { $manifest['deferred'][$service] = $provider; }
$manifest['when'][$provider] = $instance->when(); }
else { $manifest['eager'][] = $provider; } }
return $this->writeManifest($manifest); }
|
從追這些程式的過程,有發現 when()
的使用方法,但文件其實是沒有寫的。推測,可能官方還在思考要用類似 boot()
宣告方法好還是像 bindings
宣告屬性好。
不過應該還是會用宣告方法的方式,因為即使是 deferred provider,在 register provider 時期,很多情況還是直接 new 實例會比較保險,用 Application::make()
找不到依賴實例的機率還是比較高的。
今日總結
分析完 Container 與 Application 的程式碼,就可以了解 Laravel 是如何輕鬆產生實例,以及註冊 service provider 的原理等。大部分的元件都會使用到 Container,之後分析其他元件就會比較好理解了。