扩展包开发
简介
扩展包是向 Laravel 添加功能的主要方式。扩展包可以是任何东西,从像 Carbon 这样处理日期的出色方式,到像 Spatie 的 Laravel Media Library 这样允许你将文件与 Eloquent 模型关联的扩展包。
扩展包有不同的类型。有些扩展包是独立的,这意味着它们可以与任何 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
类,并包含两个方法: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 application 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
包可能具有位于 Nightshade\Views\Components
命名空间内的 Calendar
和 ColorPicker
组件。
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 将通过将组件名称转换为帕斯卡命名法来自动检测链接到此组件的类。使用“点”表示法也支持子目录。
匿名组件
如果您的包包含匿名组件,它们必须放置在包“视图”目录(由 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']);}
命令
要向 Laravel 注册您的包的 Artisan 命令,您可以使用 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
方法调用时“标记”它们来实现这一点。例如,让我们在包的服务提供者的 boot
方法中使用标签为 courier
包定义两个发布组(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