在了解 Laravel 如何自动发现包提供者和门面之前,让我们首先深入了解一下 PHP 中包的概念:
什么是Laravel扩展包
扩展包一般是一堆代码的组合,用于解决业务中的某个具体细节,扩展包可以在多个项目中重复使用,例如 tymon/jwt-auth这个包,这个是Laravel生态中很著名的一个包,很多Laravel项目都有在用,你可以在任何Laravel项目中通过Composer来使用它, 也可以在任何 laravel 项目中使用它来轻松处理jwt认证相关的业务,作者不断发布新的更新和错误修复,如果你在你的项目中使用这个包,你肯定很希望一旦他们有这些更新和修复发布,您的项目就马上同步到最新的版本,目前 Composer 已完全帮您解决了这个事情了。
Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. -- getcomposer.org
Composer 是 PHP 中的依赖管理工具。它允许您声明您的项目依赖的库,它将为您管理(安装/更新)它们。 -- getcomposer.org
Laravel 附带一个 composer.json 文件,您可以在其中需要更多包来扩展应用程序的功能,您所要做的就是在该文件的 require 或 require-dev 部分下包含您想要的包,然后运行 composer update :
{
"require": {
"tymon/jwt-auth": "2.*",
},
}
您还可以使用具有相同效果的以下命令:
composer require tymon/jwt-auth
直接安装即可。当我们通过高命令行运行安装扩展包时。
Composer 完成了它的工作并拉取了你想要的那个包的版本并将它下载到你的 vendor 目录,现在这个包的所有类和文件都被加载到你的项目中你可以立即使用它,并且每隔一段时间你可以再次运行 composer update ,Composer 将获取应用到这个包的任何更新,并自动更新项目 vendor 目录中的文件。
如果我们查看jwt-auth的github主页,我们发现jwt-auth有两个版本
如果我们对比1.x和2.x的composer.json文件,我们可以发现,在2.x中,composer多配置了如下代码
我们再去看1.x的官方文档,1.x的官方文档会要求我们在使用中,另外做如下配置
我们可以猜测,composer的laravel节点自动帮我们解决了1.x中需要另外配置服务提供者的问题
如果你经常使用composer,你会发现,一些 Laravel 包需要一些额外的步骤才能在 Laravel 项目中使用:
- Register service providers 注册服务提供者
- Register Aliases/Facades 注册别名/门面
- Publish assets 发布配置资源
为更好的理解问题,我们需要谈一下Laravel的服务提供者(Service Provider),以及扩展包的发现与自动注册机制
什么是服务提供者和门面
A service provider is responsible for binding things into Laravel's service container and informing Laravel where to load package resources such as views, configuration, and localization files. -- laravel.com Docs
服务提供者负责将扩展包绑定到 Laravel 的服务容器中,并通知 Laravel 在哪里加载包资源,如视图、配置和本地化文件。 -- laravel.com 文档
您可以在官方文档中阅读有关服务提供商的更多信息。
Facades provide a "static" interface to classes that are available in the application's service container -- laravel.com Docs
Facades 为应用程序服务容器中可用的类提供一个“静态”接口 -- laravel.com Docs
可以在官方文档中阅读有关 Facades 的更多信息。
在查找和安装/更新不同的包时,Composer 会触发多个事件,您可以订阅这些事件并运行您自己的一段代码甚至是命令行可执行文件,其中有一个事件称为 post-autoload-dump ,它会在之后直接触发composer 会生成最终的类列表,它将自动加载到你的项目中,此时 Laravel 已经可以访问所有类,并且应用程序已准备好使用加载到其中的所有包类。
在Laravel 的主 composer.json 文件中Laravel订阅了这个事件:
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover"
]
}
首先它调用 postAutoloadDump() 静态方法,该方法处理清除任何缓存服务或以前发现的包,另一件事是它运行 package:discover artisan 命令,这就是开始发现并加载扩展包的入口。
The Illuminate\Foundation\Console\PackageDiscoverCommand calls the build() method on the Illuminate\Foundation\PackageManifest class, that class is where Laravel discovers installed packages.
Illuminate\Foundation\Console\PackageDiscoverCommand 调用 Illuminate\Foundation\PackageManifest 类的 build() 方法,该类是 Laravel 发现扩展包的地方。The PackageManifest is registered into the container early in the application bootstrap, exactly from within Illuminate\Foundation\Application::registerBaseServiceProviders(), this method runs directly after a new instance of the Laravel Application is created.
PackageManifest 在应用程序引导程序的早期注册到容器中,恰好在 Illuminate\Foundation\Application::registerBaseServiceProviders() 中,此方法在创建 Laravel 应用程序的新实例后直接运行。
在PackageManifest 的 build() 方法中,Laravel 查找 vendor/composer/installed.json 文件,它由 composer 生成,并保存由 composer 安装的所有库的所有 composer.json 文件内容的完整映射,Laravel 映射该文件的内容并搜索对于包含 extra.laravel 部分的包,这时,主包就和服务提供者关联起来了
Laravel首先收集所有extra.laravel的内容,然后查看 extra.laravel.dont-discover 下的主 composer.json 文件,看看您是否决定不自动发现某些包或所有包:
这里可以将 *
添加到数组中以指示 laravel 完全停止自动发现。
所以现在 Laravel 收集了关于包的信息
是的,一旦获得所需的所有信息,Laravel就会在 bootstrap/cache/packages.php 中生成一个 PHP 文件:
<?php return array (
'tymon/jwt-auth' =>
array (
'providers' =>
array (
0 => 'Tymon\\JWTAuth\\Providers\\LaravelServiceProvider',
),
'aliases' =>
array (
'JWTAuth' => 'Tymon\\JWTAuth\\Facades\\JWTAuth',
'JWTFactory' => 'Tymon\\JWTAuth\\Facades\\JWTFactory'
),
),
);
Packages registration 扩展包的注册
Laravel 有两个引导程序,它们在 HTTP 或控制台内核启动时使用:
- \Illuminate\Foundation\Bootstrap\RegisterFacades
- \Illuminate\Foundation\Bootstrap\RegisterProviders
首先RegisterFacades使用 Illuminate\Foundation\AliasLoader 将所有Facades加载到应用容器中, 并通过上一步生成的 packages.php 文件,使用 PackageManifest::aliases() 方法来收集并注册所有的别名。
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
通过代码发现,从 config/app.php 文件加载的别名与从 PackageManifest 类加载的别名合并了。
收集好别名后,Laravel 在开始注册服务提供者,通过 RegisterProviders 引导程序调用 Foundation\Application 的 registerConfiguredProviders() 方法, Laravel 收集所有应该自动注册的包的服务提供者并注册它们。
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
通过上面代码,Laravel自动注入了config配置文件中的providers,并且所有以Illuminate开头的包都自动被注入了
至此,以上就是Laravel扩展包自动注册机制的分析。