# 扩展包开发
- [简介](#introduction)
- [Facades 注解](#a-note-on-facades)
- [发现扩展包](#package-discovery)
- [服务提供者](#service-providers)
- [资源文件](#resources)
- [配置](#configuration)
- [数据库迁移](#migrations)
- [模型工厂](#factories)
- [路由](#routes)
- [语言包](#translations)
- [视图](#views)
- [命令](#commands)
- [公共资源文件](#public-assets)
- [发布群组文件](#publishing-file-groups)
## 简介
扩展包是向 Laravel 添加功能的主要方式。扩展包可以包含很多有用的功能,比如时间处理扩展包 [Carbon](https://github.com/briannesbitt/Carbon),亦或者是提供完整 BDD 测试框架的扩展包 [Behat](https://github.com/Behat/Behat).
扩展包有很多的类型。有些扩展包是独立的,这意味着它们可以和任何 PHP 框架一起使用。Carbon 和 Behat 就是这样的独立扩展包。要在 Laravel 中使用这些扩展包只需要在 `composer.json` 文件中引入它们即可。
另外,有些扩展包只能在 Laravel 中使用,这些扩展包可能包含专门用于增强 Laravel 应用的路由、控制器、视图和配置文件。本指南主要介绍 Laravel 扩展包的开发。
### Facades 注解
当开发 Laravel 应用时,通常使用契约( contracts ) 或 facades 都可以,因为它们都提供基本相同的可测试能力。但是,在开发扩展包时,扩展包并不能访问 Laravel 提供的所有测试辅助函数。 如果你希望能够像在 Laravel 应用中一样编写扩展包的测试用例,你可以使用扩展包 [Orchestral Testbench](https://github.com/orchestral/testbench) 。
## 发现扩展包
在 Laravel 应用的 `config/app.php` 配置文件中, `providers` 选项定义了能够被 Laravel 加载的服务提供者列表。 当有人安装你的扩展包时,通常会希望此列表中包含你的服务提供者。你可以在扩展包的 `composer.json` 文件中的 `extra` 部分定义服务提供者,而不是让用户手动将你的服务提供者添加到列表中。除了服务提供者,你还可以列出你想要注册的所有 [facades](/docs/{{version}}/facades) :
"extra": {
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facade"
}
}
},
将扩展包配置为可发现之后,Laravel 将在安装时自动注册扩展包的服务提供者和 facades ,从而为扩展包的用户提供便利的安装体验。
### 选择性的发现扩展包
如果你是扩展包的使用者,想要禁止一个扩展包的发现,你可以在应用的 `composer.json` 文件中的 `extra` 部分列出这个扩展包:
"extra": {
"laravel": {
"dont-discover": [
"barryvdh/laravel-debugbar"
]
}
},
你也可以在应用的 `dont-discover` 指令中使用 `*` 字符,禁用所有扩展包的发现功能:
"extra": {
"laravel": {
"dont-discover": [
"*"
]
}
},
## 服务提供者
[服务提供者](/docs/{{version}}/providers) 将你的扩展包和Laravel 联系在一起。服务提供者负责将事物绑定到 Laravel [服务容器](/docs/{{version}}/container) 中,并告诉 Laravel 从哪里加载扩展包的资源文件,例如视图、配置文件、语言包等。
服务提供者继承了 `Illuminate\Support\ServiceProvider` 类,并包含两个方法: `register` 和 `boot`。基类 `ServiceProvider` 位于Composer 扩展包的 `illuminate/support` 中,你必须将它添加到你的扩展包依赖项中。要了解有关服务提供者的结构和用途的更多信息,请查阅 [它的文档](/docs/{{version}}/providers).
## 资源文件
### 配置
通常,你需要将扩展包的配置文件发布到应用本身的 `config` 目录中。这样使用扩展包的用户就可以轻松的重写默认配置项。要发布配置文件,只需要在服务提供者的 `boot` 方法中调用 `publishes` 方法:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/path/to/config/courier.php' => config_path('courier.php'),
]);
}
现在,当扩展包的用户执行 Laravel 的 `vendor:publish` 命令,扩展包文件将被复制到指定的目录中,发布配置后,就可以像其它配置一样被访问:
$value = config('courier.option');
> {注意} 你不应该在配置文件中定义闭包函数。当用户执行 `config:cache` Artisan 命令时,配置文件将不能被正确的序列化。
#### 扩展包默认配置
你可以将扩展包默认配置和应用的已发布副本配置合并在一起。这样扩展包用户就可以在副本配置文件中定义他们想要覆盖的配置选项。想要合并配置,只需要在服务提供者的 `register` 方法中调用 `mergeConfigFrom` 方法即可:
/**
* 在容器中注册绑定
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__.'/path/to/config/courier.php', 'courier'
);
}
> {注意} 此方法只合并配置数组的第一维。如果扩展包用户定义了多维配置数组,缺少的选项将不会被合并。
### 路由
如果你的扩展包中包含路由文件,你需要使用 `loadRoutesFrom` 方法加载它们。此方法将自动判断应用的路由是否已被缓存,如果路由已经被缓存,将不会加载你的路由文件:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/routes.php');
}
### 数据库迁移
如果你的扩展包中包含 [数据库迁移](/docs/{{version}}/migrations),你需要使用 `loadMigrationsFrom` 方法告知 Laravel 如何加载它们。 `loadMigrationsFrom` 方法只需要扩展包迁移文件路径作为唯一参数:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
}
一旦你的扩展包迁移文件被注册,当运行 `php artisan migrate` 命令时它们就会被自动执行。你不需要将它们导入到应用的 `database/migrations` 目录中。
### 模型工厂
如果你的扩展包中包含 [数据库工厂](/docs/{{version}}/database-testing#writing-factories) , 你需要使用 `loadFactoriesFrom` 方法告知 Laravel 如何加载它们。 `loadFactoriesFrom` 方法只需要扩展包模型工厂文件路径作为唯一参数:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadFactoriesFrom(__DIR__.'/path/to/factories');
}
一旦你的扩展包模型工厂被注册,你就可以在应用中使用它们:
factory(Package\Namespace\Model::class)->create();
### 语言包
如果你的扩展包中包含 [语言包文件](/docs/{{version}}/localization) ,你需要使用 `loadTranslationsFrom` 方法告知 Laravel 如何加载它们。例如,如果你的扩展包名为 `courier`,你需要将下面的内容加入到服务提供者的 `boot` 方法中:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
}
扩展包翻译约定使用 `package::file.line` 语法进行引用。因此,你可以按照下面的方式来加载 `courier` 扩展包中的 `messages` 文件的 `welcome` 行:
echo trans('courier::messages.welcome');
#### 发布语言包
如果你想要将扩展包中的语言包发布到应用的 `resources/lang/vendor` 目录中, 可以使用服务提供者的 `publishes` 方法。 `publishes` 方法接收一个包含语言包路径和对应发布位置的数组。例如,发布 `courier` 扩展包的语言包文件,操作如下:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
$this->publishes([
__DIR__.'/path/to/translations' => resource_path('lang/vendor/courier'),
]);
}
现在,当扩展包的用户执行 Laravel 的 `vendor:publish` Artisan 命令,语言包将会被发布到指定的目录中。
### 视图
想要在 Laravel 中注册你的扩展包的 [视图](/docs/{{version}}/views) , 需要告知 Laravel 视图文件的位置。 你可以使用服务提供者的 `loadViewsFrom` 方法来实现。 `loadViewsFrom` 方法接收两个参数:视图模板的路径和扩展包名。例如,如果你的扩展包名为 `courier` ,你需要将下面的内容加入到服务提供者的 `boot` 方法中:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
}
扩展包视图约定使用 `package::view` 语法进行引用。因此,一旦视图路径在服务提供者中注册成功,你可以通过下面的方式来加载 `courier` 扩展包中的 `admin` 视图:
Route::get('admin', function () {
return view('courier::admin');
});
#### 重写扩展包视图
当你使用 `loadViewsFrom` 方法时, Laravel 实际上在两个位置注册视图:应用的 `resources/views/vendor` 目录和你指定的目录。所以,还以 `courier` 扩展包为例,Laravel 将首先检查开发人员是否在 `resources/views/vendor/courier` 中提供了一个自定义版本的视图。然后,如果视图尚未定义,Laravel 将搜索在 `loadViewsFrom` 中定义的视图目录。这可以让用户轻松自定义或重写扩展包视图。
#### 发布视图
如果你希望将扩展包视图发布到应用的 `resources/views/vendor` 目录中,则可以使用服务提供者 `publishes` 方法。 `publishes` 方法接收一个包含视图路径和对应发布位置的数组:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
$this->publishes([
__DIR__.'/path/to/views' => resource_path('views/vendor/courier'),
]);
}
现在,当扩展包的用户执行 Laravel 的 `vendor:publish` Artisan 命令,扩展包视图将会被发布到指定的目录中。
## 命令
想要在 Laravel 中注册扩展包的 Artisan 命令,需要使用 `commands` 方法。此方法接收一个命令类的数组,一旦这些命令被注册成功,你可以使用 [Artisan 命令行](/docs/{{version}}/artisan) 执行他们:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
FooCommand::class,
BarCommand::class,
]);
}
}
## 公共资源文件
你的扩展包可能包含 JavaScript 、CSS 和图片之类的资源文件。要将这些资源发布到应用的 `public` 目录,可以使用服务提供者的 `publishes` 方法。在下面的例子中,我们也可以添加一个 `public` 资源组标签,该标签可用于发布相关资源组:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/path/to/assets' => public_path('vendor/courier'),
], 'public');
}
现在,当扩展包的用户执行 `vendor:publish` 命令,扩展包资源文件将会被发布到指定的目录中。 由于每次更新扩展包时通常都需要覆盖资源文件,因此需要使用`--force` 标签:
php artisan vendor:publish --tag=public --force
## 发布群组文件
你可能想要分别发布扩展包资源文件和资源。举个例子,你想要用户只发布扩展包的配置文件,而不是被强制发布扩展包中的资源文件。你可以通过调用服务提供者中 `publishes` 方法时对他们打上「标签」。例如,让我们使用扩展包服务提供者中的 `boot` 方法来定义两个发布群组:
/**
* 在注册后启动服务
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php')
], 'config');
$this->publishes([
__DIR__.'/../database/migrations/' => database_path('migrations')
], 'migrations');
}
现在,你的用户可以在执行 `vendor:publish` 命令时,通过定义的标签来分别发布这些群组:
php artisan vendor:publish --tag=config