包开发
引言
包是向 Laravel 添加功能的主要方式。包可以是任何东西,从像 Carbon 这样的优秀日期处理工具,到像 Spatie 的 Laravel Media Library 这样允许你将文件与 Eloquent 模型关联的包。
包有不同类型。有些包是独立的,这意味着它们可以与任何 PHP 框架一起工作。Carbon 和 Pest 就是独立包的例子。这些包中的任何一个都可以通过在你的 composer.json 文件中引用(require)来与 Laravel 一起使用。
另一方面,其他包是专门为 Laravel 设计的。这些包可能有专门用于增强 Laravel 应用程序的路由、控制器、视图和配置。本指南主要介绍这些特定于 Laravel 的包的开发。
关于 Facades 的说明
在编写 Laravel 应用程序时,使用 Contracts 还是 Facades 通常并不重要,因为两者都提供了基本相同的可测试性。然而,在编写包时,你的包通常无法访问所有 Laravel 的测试助手。如果你想让你的包测试起来就像安装在一个典型的 Laravel 应用程序中一样,你可以使用 Orchestral Testbench 包。
包自动发现
Laravel 应用程序的 bootstrap/providers.php 文件包含了 Laravel 应该加载的服务提供者列表。然而,为了不要求用户手动将你的服务提供者添加到列表中,你可以在你的包的 composer.json 文件的 extra 部分定义该提供者,以便 Laravel 自动加载它。除了服务提供者,你还可以列出任何你想要注册的 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": [
"*"
]
}
},
服务提供者
服务提供者 是你的包与 Laravel 之间的连接点。服务提供者负责将内容绑定到 Laravel 的 服务容器 中,并通知 Laravel 在何处加载包资源,例如视图、配置和语言文件。
服务提供者继承自 Illuminate\Support\ServiceProvider 类,并包含两个方法:register 和 boot。基础的 ServiceProvider 类位于 illuminate/support Composer 包中,你应该将它添加到你自己的包的依赖中。要了解更多关于服务提供者结构和目的的信息,请查看它们的文档。
资源
配置
通常,你需要将包的配置文件发布到应用程序的 config 目录。这将允许你的包的用户轻松覆盖你的默认配置选项。为了让你的配置文件可以发布,从你的服务提供者的 boot 方法中调用 publishes 方法:
/**
* Bootstrap any package services.
*/
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 方法接受你的包的配置文件路径作为它的第一个参数,以及应用程序中配置文件的副本名称作为它的第二个参数:
/**
* Register any package services.
*/
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/courier.php', 'courier'
);
}
路由
如果你的包包含路由,你可以使用 loadRoutesFrom 方法加载它们。这个方法会自动确定应用程序的路由是否已被缓存,如果路由已经缓存,它将不会加载你的路由文件:
/**
* Bootstrap any package services.
*/
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
迁移
如果你的包包含 数据库迁移,你可以使用 publishesMigrations 方法来告知 Laravel 给定的目录或文件包含迁移。当 Laravel 发布迁移时,它会自动更新其文件名中的时间戳以反映当前日期和时间:
/**
* Bootstrap any package services.
*/
public function boot(): void
{
$this->publishesMigrations([
__DIR__.'/../database/migrations' => database_path('migrations'),
]);
}
语言文件
如果你的包包含 语言文件,你可以使用 loadTranslationsFrom 方法来告知 Laravel 如何加载它们。例如,如果你的包名为 courier,你应该将以下内容添加到你的服务提供者的 boot 方法中:
/**
* Bootstrap any package services.
*/
public function boot(): void
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}
包翻译行使用 package::file.line 的语法约定来引用。因此,你可以像这样从 messages 文件中加载 courier 包的 welcome 行:
echo trans('courier::messages.welcome');
你可以使用 loadJsonTranslationsFrom 方法为你的包注册 JSON 翻译文件。此方法接受包含你的包的 JSON 翻译文件的目录路径:
/**
* Bootstrap any package services.
*/
public function boot(): void
{
$this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
发布语言文件
如果你想将你的包的语言文件发布到应用程序的 lang/vendor 目录,你可以使用服务提供者的 publishes 方法。publishes 方法接受一个包含包路径及其期望发布位置的数组。例如,要发布 courier 包的语言文件,你可以这样做:
/**
* Bootstrap any package services.
*/
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 方法中:
/**
* Bootstrap any package services.
*/
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 目录和你指定的目录。因此,以 courier 包为例,Laravel 首先会检查开发者是否在 resources/views/vendor/courier 目录中放置了该视图的自定义版本。然后,如果该视图没有被自定义,Laravel 将搜索你在调用 loadViewsFrom 时指定的包视图目录。这使得包用户可以轻松地自定义/覆盖你的包的视图。
发布视图
如果你想让你的视图可以发布到应用程序的 resources/views/vendor 目录,你可以使用服务提供者的 publishes 方法。publishes 方法接受一个包含包视图路径及其期望发布位置的数组:
/**
* Bootstrap the package services.
*/
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;
/**
* Bootstrap your package's services.
*/
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;
/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
这将允许使用 package-name:: 语法和它们的供应商命名空间来使用包组件:
<x-nightshade::calendar />
<x-nightshade::color-picker />
Blade 会通过将组件名称转换为 PascalCase 来自动检测与此组件链接的类。使用“点”符号也支持子目录。
匿名组件
如果你的包包含匿名组件,它们必须放在你的包的“views”目录(由 loadViewsFrom 方法指定)的 components 目录中。然后,你可以通过在组件名称前加上包的视图命名空间来渲染它们:
<x-courier::alert />
“About” Artisan 命令
Laravel 内置的 about Artisan 命令提供了应用程序环境和配置的概览。包可以通过 AboutCommand 类向该命令的输出推送额外的信息。通常,这些信息可以在你的包服务提供者的 boot 方法中添加:
use Illuminate\Foundation\Console\AboutCommand;
/**
* Bootstrap any package services.
*/
public function boot(): void
{
AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}
命令
要将你的包的 Artisan 命令注册到 Laravel,你可以使用 commands 方法。此方法需要一个命令类名称数组。一旦命令被注册,你就可以使用 Artisan CLI 来执行它们:
use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;
/**
* Bootstrap any package services.
*/
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
NetworkCommand::class,
]);
}
}
优化命令
Laravel 的 optimize 命令 会缓存应用程序的配置、事件、路由和视图。使用 optimizes 方法,你可以注册你自己的包的 Artisan 命令,这些命令应该在执行 optimize 和 optimize:clear 命令时被调用:
/**
* Bootstrap any package services.
*/
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->optimizes(
optimize: 'package:optimize',
clear: 'package:clear-optimizations',
);
}
}
公共资源
你的包可能包含 JavaScript、CSS 和图片等资源。要将这些资源发布到应用程序的 public 目录,请使用服务提供者的 publishes 方法。在这个例子中,我们还将添加一个 public 资源组标签,可用于轻松发布相关资源的组:
/**
* Bootstrap any package services.
*/
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)它们来做到这一点。例如,让我们在 courier 包的服务提供者的 boot 方法中使用标签定义两个发布组(courier-config 和 courier-migrations):
/**
* Bootstrap any package services.
*/
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"