跳转至内容

包开发

简介

包是向 Laravel 添加功能的主要方式。包可以是各种各样的东西,例如处理日期的出色工具 Carbon,或者是允许将文件关联到 Eloquent 模型的包,如 Spatie 的 Laravel Media Library

包有不同的类型。有些包是独立的,意味着它们可以与任何 PHP 框架配合使用。Carbon 和 Pest 就是独立包的示例。通过在 composer.json 文件中引用这些包,可以在 Laravel 中使用它们。

另一方面,其他包则是专门为 Laravel 设计的。这些包可能包含专门用于增强 Laravel 应用程序的路由、控制器、视图和配置。本指南主要介绍这些特定于 Laravel 的包的开发。

关于 Facades 的说明

在编写 Laravel 应用程序时,使用契约(contracts)还是门面(facades)通常并不重要,因为两者提供了基本相同的可测试性级别。但是,在编写包时,您的包通常无法访问所有 Laravel 的测试辅助函数。如果您希望像在典型的 Laravel 应用程序中安装包那样编写包测试,可以使用 Orchestral Testbench 包。

包发现

Laravel 应用程序的 bootstrap/providers.php 文件包含应由 Laravel 加载的服务提供者列表。但是,与其要求用户手动将您的服务提供者添加到列表中,不如在包的 composer.json 文件的 extra 部分中定义提供者,以便 Laravel 自动加载它。除了服务提供者,您还可以列出您希望注册的任何 门面(facades)

1"extra": {
2 "laravel": {
3 "providers": [
4 "Barryvdh\\Debugbar\\ServiceProvider"
5 ],
6 "aliases": {
7 "Debugbar": "Barryvdh\\Debugbar\\Facade"
8 }
9 }
10},

一旦您的包配置了包发现功能,Laravel 在安装时会自动注册其服务提供者和门面,从而为您的包用户创造便捷的安装体验。

禁用包发现

如果您是某个包的使用者,并希望禁用该包的发现功能,可以在应用程序的 composer.json 文件的 extra 部分中列出该包的名称。

1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "barryvdh/laravel-debugbar"
5 ]
6 }
7},

您可以使用 * 字符在应用程序的 dont-discover 指令中禁用所有包的自动发现。

1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "*"
5 ]
6 }
7},

服务提供者

服务提供者 是您的包与 Laravel 之间的连接点。服务提供者负责将内容绑定到 Laravel 的 服务容器 中,并告知 Laravel 在何处加载包资源,如视图、配置和语言文件。

服务提供者继承 Illuminate\Support\ServiceProvider 类,并包含两个方法:registerboot。基础 ServiceProvider 类位于 illuminate/support Composer 包中,您应该将其添加到自己包的依赖项中。要了解有关服务提供者结构和目的的更多信息,请查看 其文档

资源

配置

通常,您需要将包的配置文件发布到应用程序的 config 目录中。这将允许您的包的用户轻松覆盖默认配置选项。要允许发布配置文件,请在服务提供者的 boot 方法中调用 publishes 方法。

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->publishes([
7 __DIR__.'/../config/courier.php' => config_path('courier.php'),
8 ]);
9}

现在,当您的包的用户执行 Laravel 的 vendor:publish 命令时,您的文件将被复制到指定的发布位置。一旦配置被发布,就可以像访问其他配置文件一样访问其值。

1$value = config('courier.option');

您不应在配置文件中定义闭包(closures)。当用户执行 config:cache Artisan 命令时,它们无法正确序列化。

默认包配置

您还可以将自己的包配置文件与应用程序的已发布副本合并。这将允许用户仅定义他们实际上想要在已发布配置文件副本中覆盖的选项。要合并配置文件值,请在服务提供者的 register 方法中使用 mergeConfigFrom 方法。

mergeConfigFrom 方法的第一个参数是包配置文件的路径,第二个参数是应用程序中配置文件的名称。

1/**
2 * Register any package services.
3 */
4public function register(): void
5{
6 $this->mergeConfigFrom(
7 __DIR__.'/../config/courier.php', 'courier'
8 );
9}

此方法仅合并配置数组的第一层。如果用户部分定义了多维配置数组,缺失的选项将不会被合并。

路由

如果您的包包含路由,您可以使用 loadRoutesFrom 方法加载它们。此方法将自动确定应用程序的路由是否已缓存,如果路由已被缓存,则不会加载您的路由文件。

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
7}

迁移

如果您的包包含 数据库迁移,您可以使用 publishesMigrations 方法告知 Laravel 指定目录或文件包含迁移。当 Laravel 发布迁移时,它会自动更新文件名中的时间戳以反映当前的日期和时间。

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->publishesMigrations([
7 __DIR__.'/../database/migrations' => database_path('migrations'),
8 ]);
9}

语言文件

如果您的包包含 语言文件,您可以使用 loadTranslationsFrom 方法告知 Laravel 如何加载它们。例如,如果您的包名为 courier,您应该将以下内容添加到服务提供者的 boot 方法中:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
7}

包翻译行使用 package::file.line 语法约定进行引用。因此,您可以按照以下方式从 messages 文件加载 courier 包的 welcome 行:

1echo trans('courier::messages.welcome');

您可以使用 loadJsonTranslationsFrom 方法为您的包注册 JSON 翻译文件。此方法接受包含包 JSON 翻译文件的目录路径。

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
7}

发布语言文件

如果您希望将包的语言文件发布到应用程序的 lang/vendor 目录,可以使用服务提供者的 publishes 方法。publishes 方法接受一个包含包路径及其期望发布位置的数组。例如,要发布 courier 包的语言文件,您可以执行以下操作:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
7 
8 $this->publishes([
9 __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
10 ]);
11}

现在,当您的包的用户执行 Laravel 的 vendor:publish Artisan 命令时,您的包语言文件将被发布到指定的发布位置。

视图

要将包的 视图 注册到 Laravel,您需要告知 Laravel 视图所在的位置。您可以使用服务提供者的 loadViewsFrom 方法来完成此操作。loadViewsFrom 方法接受两个参数:视图模板的路径和包的名称。例如,如果包名称为 courier,您将在服务提供者的 boot 方法中添加以下内容:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
7}

包视图使用 package::view 语法约定进行引用。因此,一旦视图路径在服务提供者中注册,您就可以按照以下方式从 courier 包加载 dashboard 视图:

1Route::get('/dashboard', function () {
2 return view('courier::dashboard');
3});

覆盖包视图

当使用 loadViewsFrom 方法时,Laravel 实际上为您的视图注册了两个位置:应用程序的 resources/views/vendor 目录和您指定的目录。因此,以 courier 包为例,Laravel 将首先检查开发人员是否在 resources/views/vendor/courier 目录中放置了该视图的自定义版本。如果没有自定义视图,Laravel 将搜索您在调用 loadViewsFrom 时指定的包视图目录。这使得包用户可以轻松自定义/覆盖您的包视图。

发布视图

如果您希望使视图可发布到应用程序的 resources/views/vendor 目录,可以使用服务提供者的 publishes 方法。publishes 方法接受一个包含包视图路径及其期望发布位置的数组。

1/**
2 * Bootstrap the package services.
3 */
4public function boot(): void
5{
6 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
7 
8 $this->publishes([
9 __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
10 ]);
11}

现在,当您的包的用户执行 Laravel 的 vendor:publish Artisan 命令时,您的包视图将被复制到指定的发布位置。

视图组件

如果您正在构建利用 Blade 组件或将组件放置在非常规目录中的包,则需要手动注册组件类及其 HTML 标签别名,以便 Laravel 知道在哪里查找组件。通常,您应该在包服务提供者的 boot 方法中注册组件:

1use Illuminate\Support\Facades\Blade;
2use VendorPackage\View\Components\AlertComponent;
3 
4/**
5 * Bootstrap your package's services.
6 */
7public function boot(): void
8{
9 Blade::component('package-alert', AlertComponent::class);
10}

一旦组件注册完成,就可以使用其标签别名进行渲染:

1<x-package-alert/>

自动加载包组件

或者,您可以使用 componentNamespace 方法按约定自动加载组件类。例如,Nightshade 包可能拥有位于 Nightshade\Views\Components 命名空间内的 CalendarColorPicker 组件:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 */
6public function boot(): void
7{
8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
9}

这将允许使用 package-name:: 语法按其供应商命名空间使用包组件:

1<x-nightshade::calendar />
2<x-nightshade::color-picker />

Blade 将通过将组件名称转为帕斯卡命名法(Pascal-case)自动检测链接到此组件的类。子目录也支持使用“点”符号。

匿名组件

如果您的包包含匿名组件,它们必须放置在包“视图”目录(由 loadViewsFrom 方法 指定)的 components 目录中。然后,您可以通过在组件名称前加上包的视图命名空间来渲染它们:

1<x-courier::alert />

“About” Artisan 命令

Laravel 内置的 about Artisan 命令提供了应用程序环境和配置的摘要。包可以通过 AboutCommand 类将附加信息推送到此命令的输出中。通常,此信息可以从包服务提供者的 boot 方法中添加:

1use Illuminate\Foundation\Console\AboutCommand;
2 
3/**
4 * Bootstrap any package services.
5 */
6public function boot(): void
7{
8 AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
9}

命令

要将包的 Artisan 命令注册到 Laravel,可以使用 commands 方法。此方法需要一个包含命令类名称的数组。命令注册后,您可以使用 Artisan CLI 执行它们:

1use Courier\Console\Commands\InstallCommand;
2use Courier\Console\Commands\NetworkCommand;
3 
4/**
5 * Bootstrap any package services.
6 */
7public function boot(): void
8{
9 if ($this->app->runningInConsole()) {
10 $this->commands([
11 InstallCommand::class,
12 NetworkCommand::class,
13 ]);
14 }
15}

优化命令

Laravel 的 优化命令 会缓存应用程序的配置、事件、路由和视图。使用 optimizes 方法,您可以注册当执行 optimizeoptimize:clear 命令时应调用的包专属 Artisan 命令:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 if ($this->app->runningInConsole()) {
7 $this->optimizes(
8 optimize: 'package:optimize',
9 clear: 'package:clear-optimizations',
10 );
11 }
12}

重载命令

Laravel 的 重载命令 会终止任何正在运行的服务,以便系统进程监视器可以自动重启它们。使用 reloads 方法,您可以注册当执行 reload 命令时应调用的包专属 Artisan 命令:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 if ($this->app->runningInConsole()) {
7 $this->reloads('package:reload');
8 }
9}

公共资源

您的包可能包含 JavaScript、CSS 和图片等资源。要将这些资源发布到应用程序的 public 目录,请使用服务提供者的 publishes 方法。在此示例中,我们还将添加一个 public 资源组标签,用于轻松发布相关资源组:

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->publishes([
7 __DIR__.'/../public' => public_path('vendor/courier'),
8 ], 'public');
9}

现在,当包用户执行 vendor:publish 命令时,您的资源将被复制到指定的发布位置。由于用户通常需要在每次更新包时覆盖资源,他们可以使用 --force 标志。

1php artisan vendor:publish --tag=public --force

发布文件组

您可能希望分别发布包资源和资源的组。例如,您可能希望允许用户发布包配置文件,而不强制发布包资源。您可以通过在从包服务提供者调用 publishes 方法时对它们进行“标记(tagging)”来实现这一点。例如,让我们在包服务提供者的 boot 方法中使用标签为 courier 包定义两个发布组(courier-configcourier-migrations):

1/**
2 * Bootstrap any package services.
3 */
4public function boot(): void
5{
6 $this->publishes([
7 __DIR__.'/../config/package.php' => config_path('package.php')
8 ], 'courier-config');
9 
10 $this->publishesMigrations([
11 __DIR__.'/../database/migrations/' => database_path('migrations')
12 ], 'courier-migrations');
13}

现在,您的用户可以在执行 vendor:publish 命令时通过引用其标签来分别发布这些组:

1php artisan vendor:publish --tag=courier-config

用户还可以使用 --provider 标志发布由包的服务提供者定义的所有可发布文件:

1php artisan vendor:publish --provider="Your\Package\ServiceProvider"