包开发
介绍
包是向 Laravel 添加功能的主要方式。包可以是任何东西,从像 Carbon 这样处理日期的好方法,到像 Spatie 的 Laravel Media Library 这样允许你将文件与 Eloquent 模型关联的包。
包有不同的类型。有些包是独立的,意味着它们可以与任何 PHP 框架一起工作。Carbon 和 PHPUnit 是独立包的例子。任何这些包都可以通过在你的 composer.json
文件中要求它们来与 Laravel 一起使用。
另一方面,其他包是专门为 Laravel 使用而设计的。这些包可能有路由、控制器、视图和配置,专门用于增强 Laravel 应用程序。本指南主要涵盖那些专门为 Laravel 开发的包。
关于 Facades 的说明
在编写 Laravel 应用程序时,使用 contracts 还是 facades 通常无关紧要,因为两者都提供了基本相同的可测试性。然而,在编写包时,你的包通常无法访问所有 Laravel 的测试助手。如果你希望能够像在典型的 Laravel 应用程序中安装包一样编写包测试,你可以使用 Orchestral Testbench 包。
包发现
在 Laravel 应用程序的 config/app.php
配置文件中,providers
选项定义了 Laravel 应该加载的服务提供者列表。当有人安装你的包时,你通常希望你的服务提供者被包含在这个列表中。你可以在包的 composer.json
文件的 extra
部分中定义提供者,而不是要求用户手动将你的服务提供者添加到列表中。除了服务提供者,你还可以列出任何你希望注册的 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
方法:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/courier.php' => config_path('courier.php'),
]);
}
现在,当你的包用户执行 Laravel 的 vendor:publish
命令时,你的文件将被复制到指定的发布位置。一旦你的配置被发布,其值可以像任何其他配置文件一样访问:
$value = config('courier.option');
你不应该在配置文件中定义闭包。当用户执行 config:cache
Artisan 命令时,它们无法正确序列化。
默认包配置
你还可以将自己的包配置文件与应用程序的已发布副本合并。这将允许你的用户仅在配置文件的已发布副本中定义他们实际想要覆盖的选项。要合并配置文件值,请在服务提供者的 register
方法中使用 mergeConfigFrom
方法。
mergeConfigFrom
方法接受包的配置文件路径作为第一个参数,应用程序的配置文件副本的名称作为第二个参数:
/**
* 注册任何应用程序服务。
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__.'/../config/courier.php', 'courier'
);
}
此方法仅合并配置数组的第一级。如果你的用户部分定义了多维配置数组,缺少的选项将不会被合并。
路由
如果你的包包含路由,你可以使用 loadRoutesFrom
方法加载它们。此方法将自动确定应用程序的路由是否已缓存,并且如果路由已缓存,将不会加载你的路由文件:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
迁移
如果你的包包含 数据库迁移,你可以使用 loadMigrationsFrom
方法告知 Laravel 如何加载它们。loadMigrationsFrom
方法接受包的迁移路径作为其唯一参数:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
一旦你的包的迁移被注册,它们将在执行 php artisan migrate
命令时自动运行。你不需要将它们导出到应用程序的 database/migrations
目录。
翻译
如果你的包包含 翻译文件,你可以使用 loadTranslationsFrom
方法告知 Laravel 如何加载它们。例如,如果你的包名为 courier
,你应该在服务提供者的 boot
方法中添加以下内容:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}
包翻译使用 package::file.line
语法约定引用。因此,你可以像这样加载 courier
包的 messages
文件中的 welcome
行:
echo trans('courier::messages.welcome');
发布翻译
如果你想将包的翻译发布到应用程序的 lang/vendor
目录,你可以使用服务提供者的 publishes
方法。publishes
方法接受包路径和其期望的发布位置的数组。例如,要发布 courier
包的翻译文件,你可以执行以下操作:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
$this->publishes([
__DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
]);
}
现在,当你的包用户执行 Laravel 的 vendor:publish
Artisan 命令时,你的包的翻译将被发布到指定的发布位置。
视图
要将包的 视图 注册到 Laravel,你需要告诉 Laravel 视图的位置。你可以使用服务提供者的 loadViewsFrom
方法来实现。loadViewsFrom
方法接受两个参数:视图模板的路径和包的名称。例如,如果你的包名为 courier
,你可以在服务提供者的 boot
方法中添加以下内容:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$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
方法接受包视图路径和其期望的发布位置的数组:
/**
* 启动包服务。
*
* @return void
*/
public function boot()
{
$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;
/**
* 启动包的服务。
*
* @return void
*/
public function boot()
{
Blade::component('package-alert', AlertComponent::class);
}
一旦你的组件被注册,它可以使用其标签别名进行渲染:
<x-package-alert/>
自动加载包组件
或者,你可以使用 componentNamespace
方法通过约定自动加载组件类。例如,一个 Nightshade
包可能有 Calendar
和 ColorPicker
组件,它们位于 Nightshade\Views\Components
命名空间中:
use Illuminate\Support\Facades\Blade;
/**
* 启动包的服务。
*
* @return void
*/
public function boot()
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
这将允许使用 package-name::
语法通过其供应商命名空间使用包组件:
<x-nightshade::calendar />
<x-nightshade::color-picker />
Blade 将通过 pascal-casing 组件名称自动检测与此组件链接的类。子目录也支持使用“点”符号。
匿名组件
如果你的包包含匿名组件,它们必须放置在包的“视图”目录的 components
目录中(如 loadViewsFrom
方法 所指定)。然后,你可以通过在组件名称前加上包的视图命名空间来渲染它们:
<x-courier::alert />
"About" Artisan 命令
Laravel 内置的 about
Artisan 命令提供了应用程序环境和配置的概要。包可以通过 AboutCommand
类将附加信息推送到此命令的输出中。通常,这些信息可以从包服务提供者的 boot
方法中添加:
use Illuminate\Foundation\Console\AboutCommand;
/**
* 启动任何应用程序服务。
*
* @return void
*/
public function boot()
{
AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}
命令
要将包的 Artisan 命令注册到 Laravel,你可以使用 commands
方法。此方法期望一个命令类名称的数组。一旦命令被注册,你可以使用 Artisan CLI 执行它们:
use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
NetworkCommand::class,
]);
}
}
公共资源
你的包可能有 JavaScript、CSS 和图像等资源。要将这些资源发布到应用程序的 public
目录,请使用服务提供者的 publishes
方法。在此示例中,我们还将添加一个 public
资产组标签,可以用来轻松发布相关资产组:
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../public' => public_path('vendor/courier'),
], 'public');
}
现在,当你的包用户执行 vendor:publish
命令时,你的资产将被复制到指定的发布位置。由于用户通常需要在每次更新包时覆盖资产,你可以使用 --force
标志:
php artisan vendor:publish --tag=public --force
发布文件组
你可能希望单独发布包资产和资源组。例如,你可能希望允许用户发布包的配置文件,而不必强制发布包的资产。你可以通过在包的服务提供者的 publishes
方法中调用时“标记”它们来实现。例如,让我们使用标签在包的服务提供者的 boot
方法中为 courier
包定义两个发布组(courier-config
和 courier-migrations
):
/**
* 启动任何包服务。
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php')
], 'courier-config');
$this->publishes([
__DIR__.'/../database/migrations/' => database_path('migrations')
], 'courier-migrations');
}
现在你的用户可以通过在执行 vendor:publish
命令时引用其标签来单独发布这些组:
php artisan vendor:publish --tag=courier-config