跳至内容

软件包开发

介绍

软件包是为 Laravel 添加功能的主要方式。软件包可以是任何东西,从处理日期的优秀方法(如 Carbon)到允许你将文件与 Eloquent 模型关联的软件包(如 Spatie 的 Laravel Media Library)。

存在不同类型的软件包。一些软件包是独立的,这意味着它们可以与任何 PHP 框架配合使用。Carbon 和 Pest 是独立软件包的示例。任何这些软件包都可以在你的 composer.json 文件中声明后,与 Laravel 一起使用。

另一方面,其他软件包专门用于与 Laravel 配合使用。这些软件包可能具有专门用于增强 Laravel 应用程序的路由、控制器、视图和配置。本指南主要介绍这些专门针对 Laravel 开发的软件包。

关于门面的说明

在编写 Laravel 应用程序时,使用契约或门面通常没有区别,因为两者都提供了本质上相同的可测试性级别。但是,在编写软件包时,你的软件包通常无法访问 Laravel 的所有测试助手。如果你想能够像在典型的 Laravel 应用程序中安装软件包一样编写软件包测试,可以使用 Orchestral Testbench 软件包。

软件包发现

Laravel 应用程序的 bootstrap/providers.php 文件包含 Laravel 应加载的服务提供者列表。但是,无需让用户手动将你的服务提供者添加到列表中,你可以在软件包的 composer.json 文件的 extra 部分定义提供者,以便 Laravel 自动加载它。除了服务提供者之外,你还可以列出想要注册的任何 门面

"extra": {
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facade"
}
}
},

一旦你的软件包配置好进行发现,Laravel 在安装它时将自动注册其服务提供者和门面,为你的软件包用户创造便捷的安装体验。

选择不进行软件包发现

如果你是一个软件包的使用者,并且希望禁用软件包的软件包发现,可以在你的应用程序的 composer.json 文件的 extra 部分列出软件包名称

"extra": {
"laravel": {
"dont-discover": [
"barryvdh/laravel-debugbar"
]
}
},

可以使用应用程序的 dont-discover 指令中的 * 字符,为所有软件包禁用软件包发现

"extra": {
"laravel": {
"dont-discover": [
"*"
]
}
},

服务提供者

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

服务提供者扩展了 Illuminate\Support\ServiceProvider 类,并包含两个方法:registerbootServiceProvider 的基本类位于 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');
exclamation

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

默认软件包配置

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

mergeConfigFrom 方法将你的软件包的配置文件路径作为第一个参数,将应用程序的配置文件副本的名称作为第二个参数

/**
* Register any application services.
*/
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/courier.php', 'courier'
);
}
exclamation

此方法仅合并配置数组的第一级。如果你的用户部分定义了多维配置数组,则不会合并缺少的选项。

路由

如果你的软件包包含路由,可以使用 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 语法约定引用。因此,你可以像这样加载 courier 软件包的 messages 文件中的 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 包可能具有位于 Nightshade\Views\Components 命名空间内的 CalendarColorPicker 组件。

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 将自动检测与该组件关联的类,通过将组件名称转换为帕斯卡命名法。子目录也支持,使用“点”表示法。

匿名组件

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

<x-courier::alert />

"关于" Artisan 命令

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

use Illuminate\Foundation\Console\AboutCommand;
 
/**
* Bootstrap any application 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 命令,这些命令应该在执行 optimizeoptimize: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 方法时对它们进行“标记”来做到这一点。例如,让我们使用标签为 courier 包(courier-configcourier-migrations)定义两个发布组,在包的服务提供者的 boot 方法中。

/**
* 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