扩展包开发
包开发(Package Development)
介绍
包(Packages)是向 Laravel 添加功能的主要方式。包可以是任意功能,例如处理日期的优秀包 Carbon,或者允许你将文件与 Eloquent 模型关联的包,如 Spatie 的 Laravel Media Library。
包有不同类型。有些包是独立的(stand-alone),意味着它们可以在任何 PHP 框架中使用。Carbon 和 Pest 就是独立包的例子。你可以通过在 composer.json
文件中引入它们,在 Laravel 中使用这些独立包。
另一方面,也有一些包是专为 Laravel 使用而设计的。这类包可能包含路由、控制器、视图和专门用于增强 Laravel 应用的配置。本指南主要覆盖这些专为 Laravel 开发的包。
关于 Facades 的说明(A Note on Facades)
在编写 Laravel 应用时,使用契约(contracts)或 Facades 一般差别不大,因为两者在测试可行性上基本相当。然而,在编写包时,你的包通常无法直接访问 Laravel 的所有测试辅助工具。如果你希望在编写包的测试时,就像包已经安装在典型的 Laravel 应用中一样,可以使用 Orchestral Testbench 包。
包自动发现(Package Discovery)
在 Laravel 应用中,bootstrap/providers.php
文件用于列出需要加载的服务提供者(Service Providers)。通常情况下,用户需要手动将你的服务提供者添加到这个列表中,但你可以通过在你的包的 composer.json
文件中配置 extra
部分,实现 自动发现,让 Laravel 安装包时自动注册服务提供者和 Facade,从而大大简化安装流程。
例如,你可以在 composer.json
中这样配置:
"extra": {
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facade"
}
}
},
一旦你的包配置了自动发现,当它被安装时,Laravel 会自动注册其服务提供者和 facade,从而为你的包的用户创建一个便利的安装体验。
选择退出包自动发现
如果你是包的使用者,并且希望禁用某个包的自动发现,你可以在你应用的 composer.json
文件的 extra
部分列出该包的名称:
"extra": {
"laravel": {
"dont-discover": [
"barryvdh/laravel-debugbar"
]
}
},
你也可以使用 *
字符在应用的 dont-discover
指令中禁用所有包的自动发现:
"extra": {
"laravel": {
"dont-discover": [
"*"
]
}
},
服务提供者
服务提供者 是你的包与 Laravel 之间的连接点。服务提供者负责将内容绑定到 Laravel 的 服务容器 中,并告诉 Laravel 从哪里加载包资源,例如视图、配置和语言文件。
服务提供者继承自 Illuminate\Support\ServiceProvider
类,并包含两个方法:register
和 boot
。基础的 ServiceProvider
类位于 illuminate/support
Composer 包中,你应该将其添加到你自己包的依赖中。想了解更多关于服务提供者的结构和用途,请查看 官方文档。
资源
配置
通常情况下,你需要将包的配置文件发布到应用程序的 config
目录下。这将允许在使用包时覆盖扩展包中的默认配置选项。要允许发布配置文件,需要在服务提供者的 boot
方法中调用 publishes
方法:
/**
* 引导包服务
*/
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/courier.php' => config_path('courier.php'),
]);
}
现在,当包用户执行 Laravel 的 vendor:publish
命令时,配置文件将被复制到指定的发布位置。当配置发布后,它的值可以像其他的配置文件一样被访问:
$value = config('courier.option');
警告
你不应该在你的配置文件中定义闭包。当用户执行config:cache
Artisan 命令时,它们不能被正确序列化。
默认的包配置
你也可以将你自己的包的配置文件与应用程序的发布副本合并。这将允许你的用户在配置文件的发布副本中只定义他们真正想要覆盖的选项。要合并配置文件的值,请在你的服务提供者的 register
方法中使用 mergeConfigFrom
方法。
mergeConfigFrom
方法的第一个参数是你的包的配置文件的路径,第二个参数是应用程序的配置文件副本的名称:
/**
* 注册应用服务
*/
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/courier.php', 'courier'
);
}
警告
这个方法只合并了配置数组的第一层。如果你的用户部分地定义了一个多维的配置数组,缺少的选项将不会被合并。
路由(Routes)
如果你的包中包含路由文件,可以使用 loadRoutesFrom
方法加载它们。该方法会自动检测应用的路由是否已经缓存,如果已经缓存,则不会重复加载路由文件:
/**
* 启动包相关服务
*/
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
数据迁移(Migrations)
如果你的包包含 数据库迁移,可以使用 publishesMigrations
方法告诉 Laravel 哪个目录或文件包含迁移。当 Laravel 发布迁移文件时,会自动更新文件名中的时间戳为当前时间:
/**
* 启动包相关服务
*/
public function boot(): void
{
$this->publishesMigrations([
__DIR__.'/../database/migrations' => database_path('migrations'),
]);
}
语言文件(Language Files)
如果你的包包含 语言文件,可以使用 loadTranslationsFrom
方法告诉 Laravel 如何加载它们。例如,如果你的包名为 courier
,可以在服务提供者的 boot
方法中添加:
/**
* 启动包相关服务
*/
public function boot(): void
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}
在包中使用语言行时,遵循 package::file.line
的语法。例如,要加载 courier
包的 messages
文件中的 welcome
行:
echo trans('courier::messages.welcome');
如果你的包使用 JSON 格式的翻译文件,可以使用 loadJsonTranslationsFrom
方法注册:
/**
* 启动包相关服务
*/
public function boot(): void
{
$this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
这样 Laravel 就会自动加载你的包中的 JSON 翻译文件,方便国际化使用
发布语言文件
如果你想把自己包中的语言文件发布到应用程序的 lang/vendor
目录,可以使用服务提供者的 publishes
方法。
publishes
方法接受一个数组,数组的键是包内文件路径,值是对应的目标发布位置。
例如,要发布 courier
包的语言文件,可以这样写:
/**
* 启动任意包的服务。
*/
public function boot(): void
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
$this->publishes([
__DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
]);
}
现在,当你的包的使用者执行 Laravel 的 vendor:publish
Artisan 命令时,你的包的语言文件就会被发布到指定的路径。
视图
要在 Laravel 中注册你包的 视图,需要告诉 Laravel 这些视图的位置。
你可以通过服务提供者的 loadViewsFrom
方法来实现。
loadViewsFrom
方法接受两个参数:视图模板的路径和你的包的名称。
例如,如果你的包名是 courier
,就可以在服务提供者的 boot
方法中这样写:
/**
* 启动任意包的服务。
*/
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
包中的视图是通过 package::view
语法规则来引用的。
所以,一旦你在服务提供者中注册了视图路径,就可以这样加载 courier
包中的 dashboard
视图:
Route::get('/dashboard', function () {
return view('courier::dashboard');
});
覆盖包视图
当你使用 loadViewsFrom
方法时,Laravel 实际上会为你的视图注册两个位置:
- 应用的
resources/views/vendor
目录 - 你在
loadViewsFrom
中指定的目录
因此,继续以 courier
包为例,Laravel 会先检查开发者是否在 resources/views/vendor/courier
目录下放置了一个自定义版本的视图。
如果该视图没有被自定义,Laravel 才会去查找你在 loadViewsFrom
调用中指定的包视图目录。
这样一来,包的使用者就可以很方便地自定义或覆盖你的包的视图。
发布视图
如果你希望让你的包的视图可以发布到应用的 resources/views/vendor
目录,可以使用服务提供者的 publishes
方法。
publishes
方法接受一个数组,数组的键是包内视图路径,值是对应的发布位置:
/**
* 启动包的服务。
*/
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
]);
}
现在,当你的包的使用者执行 Laravel 的 vendor:publish
Artisan 命令时,你包中的视图将会被复制到指定的发布位置。
视图组件
如果你正在构建一个使用 Blade 组件的包,或者将组件放在了非约定的目录中,就需要手动注册你的组件类及其 HTML 标签别名,以便 Laravel 知道如何找到该组件。
通常情况下,你应该在包的服务提供者的 boot
方法中注册组件:
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
/**
* 启动包的服务。
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}
一旦你的组件被注册,就可以使用它的标签别名进行渲染:
<x-package-alert/>
自动加载包组件
作为替代方案,你也可以使用 componentNamespace
方法通过约定来自动加载组件类。
例如,一个 Nightshade
包可能包含 Calendar
和 ColorPicker
组件,这些组件位于 Nightshade\Views\Components
命名空间中:
use Illuminate\Support\Facades\Blade;
/**
* 启动包的服务。
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
这样一来,就可以通过供应商命名空间并使用 package-name::
语法来调用包组件:
<x-nightshade::calendar />
<x-nightshade::color-picker />
Blade 会自动检测与该组件相关联的类,方法是将组件名转换为 PascalCase(帕斯卡命名法)。
子目录也可以通过 “点” 符号表示来支持。
匿名组件
如果你的包包含匿名组件,它们必须被放置在包的 “views” 目录下的 components
目录中(该目录位置由 loadViewsFrom 方法 指定)。
然后,你就可以在渲染时使用包的视图命名空间作为前缀:
<x-courier::alert />
"About" Artisan 命令
Laravel 内置的 about
Artisan 命令会提供应用程序环境和配置的概要。
包可以通过 AboutCommand
类向该命令的输出中追加额外信息。
通常,这些信息会在你包的服务提供者的 boot
方法中添加:
use Illuminate\Foundation\Console\AboutCommand;
/**
* 启动任意应用服务。
*/
public function boot(): void
{
AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}
命令
要在 Laravel 中注册你包的 Artisan 命令,可以使用 commands
方法。
该方法接受一个命令类名数组。
一旦命令注册完成,你就可以通过 Artisan CLI 来执行它们:
use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;
/**
* 启动任意包的服务。
*/
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
NetworkCommand::class,
]);
}
}
优化命令
Laravel 的 optimize 命令 会缓存应用的配置、事件、路由和视图。
通过 optimizes
方法,你可以注册你包自己的 Artisan 命令,使其在 optimize
和 optimize:clear
命令执行时被调用:
/**
* 启动任意包的服务。
*/
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->optimizes(
optimize: 'package:optimize',
clear: 'package:clear-optimizations',
);
}
}
公共资源
你的包可能有如 JavaScript、CSS 和图像之类的前端资源。要将这些资源发布到应用程序的 public
目录,请使用服务提供者的 publishes
方法。在此示例中,我们还将添加一个 public
资源组标签,该标签可以用于轻松发布相关资源组:
/**
* 引导任何包服务
*/
public function boot(): void
{
$this->publishes([
__DIR__.'/../public' => public_path('vendor/courier'),
], 'public');
}
现在,当你的包用户运行 vendor:publish
命令时,你的资源将被复制到指定的发布位置。由于用户通常需要每次更新包时覆盖资源,你可以使用 --force
标志:
php artisan vendor:publish --tag=public --force
发布文件组
你可能想要分别发布包资源和资源组。例如,你可能希望允许用户发布包的配置文件而不强迫发布包的资源。当从包的服务提供者调用 publishes
方法时,你可以通过「tagging」它们来实现这一点。例如,让我们在包服务提供者的 boot
方法中使用标签为 courier
包定义两个发布组(courier-config
和 courier-migrations
):
/**
* 引导包服务
*/
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php')
], 'courier-config');
$this->publishesMigrations([
__DIR__.'/../database/migrations/' => database_path('migrations')
], 'courier-migrations');
}
现在,你的用户可以通过在执行 vendor:publish
命令时引用其标签,分别发布这些组:
php artisan vendor:publish --tag=courier-config
你的用户还可以使用 --provider
标志发布包服务提供商定义的所有可发布文件:
php artisan vendor:publish --provider="Your\Package\ServiceProvider"
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: