Laravel 自动发现扩展包是怎样实现的
我们都知道,在 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 扩展包自动发现的原理了。
参考
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: