# 扩展包开发
- [简介](#introduction)
- [Facades 注解](#a-note-on-facades)
- [发现扩展包](#package-discovery)
- [服务提供者](#service-providers)
- [资源文件](#resources)
- [配置](#configuration)
- [数据库迁移](#migrations)
- [路由](#routes)
- [语言包](#translations)
- [视图](#views)
- [命令](#commands)
- [公共资源文件](#public-assets)
- [发布群组文件](#publishing-file-groups)
<a name="introduction"></a>
## 简介
扩展包是向 Laravel 中添加功能的主要方式。扩展包可以包含很多有用的功能,例如时间处理扩展包 [Carbon](https://github.com/briannesbitt/Carbon),或提供完整 BDD 测试框架的扩展包 [Behat](https://github.com/Behat/Behat)。
当然,扩展包有很多种类型。有些扩展包是独立运行的,意味着他们可以在任意的 PHP 框架中使用。Carbon 和 Behat 就是这样的独立扩展包。要在 Laravel 中使用这种扩展包只需要在 `composer.json` 文件中引入他们即可。
另一方面,有些扩展包只能在 Laravel 中使用。这些扩展包可能包含专门用来增强 Laravel 应用的路由、控制器、视图和配置的文件。这份指南主要介绍 Laravel 扩展包的开发。
<a name="a-note-on-facades"></a>
### Facades 注解
当开发 Laravel 应用时,通常使用契约(contracts)或 facades 没有什么区别,因为他们都提供基本相同的可测试能力。然而,在进行扩展包开发的时候,扩展包并不能访问 Laravel 提供的所有测试辅助函数。如果你想像在 Laravel 应用中一样编写扩展包的测试用例,你可以使用扩展包 [Orchestral Testbench](https://github.com/orchestral/testbench)。
<a name="package-discovery"></a>
## 发现扩展包
在 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": [
"*"
]
}
},
<a name="service-providers"></a>
## 服务提供者
[服务提供者](/docs/{{version}}/providers) 让你的扩展包与 Laravel 联系在一起。服务提供者负责将一些东西绑定到 Laravel 的 [服务容器](/docs/{{version}}/container) 中,并且告诉 Laravel 从哪里加载扩展包的资源文件,例如视图、配置文件、语言包等。
服务提供者继承了 `Illuminate\Support\ServiceProvider` 类,并包含了两个方法:`register` 和 `boot`。基类 `ServiceProvider` 位于名为 `illuminate/support` 的 Composer 扩展包中,你必须将它加入到你的扩展包依赖。想要了解更多关于服务提供者的结构和用途,请查阅 [它的文档](/docs/{{version}}/providers).
<a name="resources"></a>
## 资源文件
<a name="configuration"></a>
### 配置
有时,你需要将扩展包配置文件发布到应用本身的 `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');
> {note} 你不应该在配置文件中定义闭包函数。因为当用户执行 `config:cache` Artisan 命令时,配置文件将不能被正确的序列化。
#### 扩展包默认配置
你也可以将扩展包默认配置与应用的副本配置合并在一起。这样扩展包用户就可以在副本配置文件中定义他们想要覆盖的配置选项。想要合并配置,只需要在服务提供者的 `register` 方法中调用 `mergeConfigFrom` 方法即可:
/**
* 在容器中注册绑定。
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__.'/path/to/config/courier.php', 'courier'
);
}
> {note} 此方法只合并配置数组的第一维。如果扩展包用户定义了多维配置数组,缺少的选项将不会被合并。
<a name="routes"></a>
### 路由
如果你的扩展包中包含路由文件,你需要使用 `loadRoutesFrom` 方法加载他们。此方法将自动判断应用的路由是否已被缓存,如果路由已缓存,将不会加载你的路由文件:
/**
* 在注册后启动服务。
*
* @return void
*/
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/routes.php');
}
<a name="migrations"></a>
### 数据库迁移
如果你的扩展包中包含 [数据库迁移](/docs/{{version}}/migrations),你需要使用 `loadMigrationsFrom` 方法告知 Laravel 如何加载他们。`loadMigrationsFrom` 方法只需要扩展包迁移文件路径作为唯一参数:
/**
* 在注册后启动服务。
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
}
一旦你的扩展包迁移文件被注册,当运行 `php artisan migrate` 命令时他们就会被自动执行。你不需要将他们导入到应用的 `database/migrations` 目录中。
<a name="translations"></a>
### 语言包
如果你的扩展包中包含 [语言包文件](/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 命令,语言包将会被发布到指定的目录中。
<a name="views"></a>
### 视图
想要在 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 命令,视图将会被发布到自定的目录中。
<a name="commands"></a>
## 命令
想要在 Laravel 中注册扩展包的 Artisan 命令,需要使用 `commands` 方法。此方法接收一个命令类的数组。一旦这些命令注册成功,可以使用 [Artisan 命令行](/docs/{{version}}/artisan) 执行他们:
/**
* 引导应用服务。
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
FooCommand::class,
BarCommand::class,
]);
}
}
<a name="public-assets"></a>
## 公共资源文件
你的扩展包中可能存在 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
<a name="publishing-file-groups"></a>
## 发布群组文件
你可能想要分别打包发布扩展包资源文件或资源。举个例子,你想让用户单独发布扩展包中的配置文件,而不是被强制发布扩展包中的所有资源文件。你可以通过调用扩展包服务提供者中的 `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
- 入门指南
- 安装
- 部署
- 基础功能
- 路由
- 中间件
- CSRF 保护
- 控制器
- 请求
- 响应
- 视图
- URL
- Session
- 表单验证
- 错误
- 日志
- 前端开发
- Blade 模板
- 本地化
- 脚手架
- 编译资源 Mix
- 安全相关
- 用户认证
- API 认证
- 综合话题
- 命令行
- 广播
- 缓存
- 集合
- 事件
- 文件存储
- 辅助函数
- 邮件发送
- 消息通知
- 扩展包开发
- 队列
- 任务调度
- 数据库
- 快速入门
- 查询构造器
- 分页
- 数据库迁移
- 数据填充
- Redis
- Eloquent ORM
- 快速入门
- 速查表
- Artisan
- Auth
- Blade
- Cache
- Collection
- Composer
- Config
- Container
- Cookie
- DB
- Environment
- Event
- File
- Helper
- Input
- Lang
- Log
- Model
- Pagination
- Queue
- Redirect
- Request
- Response
- Route
- SSH
- Schema
- Security
- Session
- Storage
- String
- URL
- UnitTest
- Validation
- View