我們都知道,在 Laravel5.5 版本以後加入了一項新功能——自動發現擴充套件包。簡單來說就是之前我們在引入第三方擴充套件的時候需要在config/app.php
中將對應的 Provider 和 Facade 進行註冊,而在5.5之後我們就無需進行這樣的操作了。下面我們分析一下它的實現原理。
composer指令碼
那麼什麼是composer指令碼呢?
一個指令碼,在 Composer 中,可以是一個 PHP 回撥(定義為靜態方法)或任何命令列可執行的命令。指令碼對於在 Composer 執行過程中,執行一個資源包的自定義程式碼或包專用命令是非常有用的。
—— 出自 Composer 文件
在 composer 執行過程中會觸發一系列的事件(事件列表可以到(這裡)檢視),我們可以通過事件來觸發對應的指令碼。
我們在 Laravel 專案的 composer.json
檔案中可以看到這樣一段程式碼:
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
上面的程式碼表示當我們執行composer install
、composer update
或者composer dump-autoload
的時候就會觸發陣列內的這兩個命令。
postAutoloadDump
這個命令很簡單,清空快取的服務和之前自動發現的擴充套件。程式碼如下:
public static function postAutoloadDump(Event $event)
{
require_once $event->getComposer()->getConfig()->get('vendor-dir').'/autoload.php';
static::clearCompiled();
}
protected static function clearCompiled()
{
$laravel = new Application(getcwd());
if (file_exists($servicesPath = $laravel->getCachedServicesPath())) {
@unlink($servicesPath);
}
if (file_exists($packagesPath = $laravel->getCachedPackagesPath())) {
@unlink($packagesPath);
}
}
這個執行完畢之後接著執行php artisan package:discover
命令。
擴充套件包發現
package:discover
命令在Illuminate\Foundation\Console\PackageDiscoverCommand
中,具體如下:
use Illuminate\Foundation\PackageManifest;
public function handle(PackageManifest $manifest)
{
$manifest->build();
foreach (array_keys($manifest->manifest) as $package) {
$this->line("Discovered Package: <info>{$package}</info>");
}
$this->info('Package manifest generated successfully.');
}
這個方法呼叫了 Illuminate\Foundation\PackageManifest
類中的 build
方法。在這裡 Laravel 處理擴充套件包的自動發現。
public function build()
{
$packages = [];
if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) {
$packages = json_decode($this->files->get($path), true);
}
$ignoreAll = in_array('*', $ignore = $this->packagesToIgnore());
$this->write(collect($packages)->mapWithKeys(function ($package) {
return [$this->format($package['name']) => $package['extra']['laravel'] ?? []];
})->each(function ($configuration) use (&$ignore) {
$ignore = array_merge($ignore, $configuration['dont-discover'] ?? []);
})->reject(function ($configuration, $package) use ($ignore, $ignoreAll) {
return $ignoreAll || in_array($package, $ignore);
})->filter()->all());
}
在這個方法中,Laravel 首先查詢 vendor/composer/installed.json
檔案,這個檔案是由 composer 生成的,裡面包含了一份完整的已經安裝完成的擴充套件包資訊,Laravel 在這個檔案中查詢所有包含 extra.laravel
的部分:
"extra": {
"laravel": {
"providers": [
"BeyondCode\\DumpServer\\DumpServerServiceProvider"
]
}
}
Laravel獲取到包含有 extra.laravel
的內容之後,然後查詢專案中composer.json
中的extra.laravel.dont-discover
部分,將裡面的內容忽略掉。如果你想將所有的擴充套件包禁用自動發現,改為下面的配置即可, 即'*'
的部分。
"extra": {
"laravel": {
"dont-discover": ['*']
}
},
當內容收集完畢之後,Laravel 會將其寫入到 bootstrap/cache/packages.php
檔案:
<?php
return [
'beyondcode/laravel-dump-server' => [
'providers' => [
0 => 'BeyondCode\\DumpServer\\DumpServerServiceProvider',
],
]
];
擴充套件包註冊
Laravel使用了2個引導類來完成 Provider 和 Facade 的註冊:
\Illuminate\Foundation\Bootstrap\RegisterFacades
\Illuminate\Foundation\Bootstrap\RegisterProviders
第一個使用了 Illuminate\Foundation\AliasLoader
來載入所需要的內容。其中包括packages.php
檔案,而針對這個檔案的查詢是通過 PackageManifest::aliases()
來處理的。
// in RegisterFacades::bootstrap()
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
與之類似, Provider 的處理方法則是通過 RegisterProviders
中的 bootstrap
方法。
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
其中呼叫了 Foundation\Application
中的 registerConfiguredProviders
方法:
public function registerConfiguredProviders()
{
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
這基本上就是 Laravel 擴充套件包自動發現的原理了。