什么是包
在 Laravel 中,包是通过 Composer 分发、用于向应用添加功能的组件。包大致分为两类:
独立包 — 不依赖 Laravel 的通用 PHP 库(如 Carbon、Pest)
Laravel 包 — 包含路由、Controller、视图、配置等与 Laravel 集成的功能
本指南聚焦于后者,即 Laravel 专用包的开发。包开发需要深入理解 Service Provider、Facade、配置发布等 Laravel 内部结构。
包的自动发现
Laravel 在安装包时会读取 composer.json 的 extra.laravel 段,自动注册 Service Provider 与 Facade。
"extra" : {
"laravel" : {
"providers" : [
"Acme \\ Courier \\ CourierServiceProvider"
],
"aliases" : {
"Courier" : "Acme \\ Courier \\ Facades \\ Courier"
}
}
}
加入这段配置后,用户无需手动编辑 bootstrap/providers.php,包也会被自动加载。
禁用自动发现
用户想要禁用某个包的自动发现,可以在应用的 composer.json 中设置。
"extra" : {
"laravel" : {
"dont-discover" : [
"acme/courier"
]
}
}
Service Provider 的作用
Service Provider 是包的入口。将视图、配置、迁移、路由等资源注册到 Laravel 的处理都集中在这里。
Service Provider 继承 Illuminate\Support\ServiceProvider,包含 register 与 boot 两个方法。
<? php
namespace Acme\Courier ;
use Illuminate\Support\ ServiceProvider ;
class CourierServiceProvider extends ServiceProvider
{
/**
* 注册包的服务
*/
public function register () : void
{
// 服务容器绑定写在这里
$this -> mergeConfigFrom (
__DIR__ . '/../config/courier.php' , 'courier'
);
$this -> app -> singleton ( CourierManager :: class , function ( $app ) {
return new CourierManager ( $app [ 'config' ][ 'courier' ]);
});
}
/**
* 引导包的服务
*/
public function boot () : void
{
// 资源注册写在这里
$this -> loadRoutesFrom ( __DIR__ . '/../routes/web.php' );
$this -> loadViewsFrom ( __DIR__ . '/../resources/views' , 'courier' );
$this -> loadTranslationsFrom ( __DIR__ . '/../lang' , 'courier' );
$this -> publishesMigrations ([
__DIR__ . '/../database/migrations' => database_path ( 'migrations' ),
]);
$this -> publishes ([
__DIR__ . '/../config/courier.php' => config_path ( 'courier.php' ),
], 'courier-config' );
$this -> publishes ([
__DIR__ . '/../resources/views' => resource_path ( 'views/vendor/courier' ),
], 'courier-views' );
}
}
请勿在 register 方法中注册事件监听器、路由、视图。此时可能会误用尚未加载的其他 Service Provider 提供的服务。绑定之外的处理必须写在 boot 方法中。
发布配置文件
publishes() — 发布文件
在 boot 方法中调用 publishes(),用户可以通过 vendor:publish 命令将配置文件复制到自己的应用中。
public function boot () : void
{
$this -> publishes ([
__DIR__ . '/../config/courier.php' => config_path ( 'courier.php' ),
]);
}
发布后的配置值可以像普通 config 一样访问。
$value = config ( 'courier.option' );
mergeConfigFrom() — 与默认值合并
在 register 方法中使用 mergeConfigFrom(),可以让即便用户未发布配置文件,也能使用包提供的默认值。
public function register () : void
{
$this -> mergeConfigFrom (
__DIR__ . '/../config/courier.php' , 'courier'
);
}
mergeConfigFrom() 不会合并到嵌套数组的深层。对多维数组的配置,用户只定义了一部分时,剩下的选项可能不会被合并。
用 tag 划分发布组
publishes() 第 2 个参数可以指定 tag,用户就能只选择所需资源进行发布。
public function boot () : void
{
$this -> publishes ([
__DIR__ . '/../config/courier.php' => config_path ( 'courier.php' ),
], 'courier-config' );
$this -> publishesMigrations ([
__DIR__ . '/../database/migrations/' => database_path ( 'migrations' ),
], 'courier-migrations' );
}
# 仅发布配置文件
php artisan vendor:publish --tag=courier-config
# 发布 Provider 提供的全部文件
php artisan vendor:publish --provider= "Acme\Courier\CourierServiceProvider"
路由的注册
使用 loadRoutesFrom() 加载路由文件。若应用启用了路由缓存,则会自动跳过。
public function boot () : void
{
$this -> loadRoutesFrom ( __DIR__ . '/../routes/web.php' );
}
路由文件中指定包的 Controller。
// routes/web.php
use Acme\Courier\Http\Controllers\ TrackingController ;
use Illuminate\Support\Facades\ Route ;
Route :: prefix ( 'courier' ) -> group ( function () {
Route :: get ( '/track/{id}' , [ TrackingController :: class , 'show' ])
-> name ( 'courier.track' );
});
发布迁移
使用 publishesMigrations() 可以发布迁移文件。Laravel 会在发布时自动更新时间戳。
public function boot () : void
{
$this -> publishesMigrations ([
__DIR__ . '/../database/migrations' => database_path ( 'migrations' ),
]);
}
发布视图
loadViewsFrom() — 注册视图
使用 loadViewsFrom() 注册视图目录。通过第 2 个参数的命名空间,以 package::view 形式引用视图。
public function boot () : void
{
$this -> loadViewsFrom ( __DIR__ . '/../resources/views' , 'courier' );
}
注册后按包命名空间引用视图。
Route :: get ( '/dashboard' , function () {
return view ( 'courier::dashboard' );
});
Laravel 会从两处查找视图:先检查应用的 resources/views/vendor/courier 目录,若不存在再使用包内视图目录。这样用户就可以自定义视图。
发布视图
public function boot () : void
{
$this -> loadViewsFrom ( __DIR__ . '/../resources/views' , 'courier' );
$this -> publishes ([
__DIR__ . '/../resources/views' => resource_path ( 'views/vendor/courier' ),
], 'courier-views' );
}
注册 Blade 组件
在包中包含 Blade 组件时,在 boot 方法中注册。
use Illuminate\Support\Facades\ Blade ;
use Acme\Courier\View\Components\ AlertComponent ;
public function boot () : void
{
Blade :: component ( 'courier-alert' , AlertComponent :: class );
}
也可以通过命名空间批量注册。
use Illuminate\Support\Facades\ Blade ;
public function boot () : void
{
Blade :: componentNamespace ( 'Acme \\ Courier \\ View \\ Components' , 'courier' );
}
{{-- 单独注册时 --}}
< x-courier-alert />
{{-- 命名空间注册时 --}}
< x-courier::alert />
发布翻译文件
使用 loadTranslationsFrom() 注册翻译文件。翻译以 package::file.key 形式引用。
public function boot () : void
{
$this -> loadTranslationsFrom ( __DIR__ . '/../lang' , 'courier' );
$this -> publishes ([
__DIR__ . '/../lang' => $this -> app -> langPath ( 'vendor/courier' ),
]);
}
// 使用翻译
echo trans ( 'courier::messages.welcome' );
若使用 JSON 翻译文件,请使用 loadJsonTranslationsFrom()。
public function boot () : void
{
$this -> loadJsonTranslationsFrom ( __DIR__ . '/../lang' );
}
命令的注册
包的 Artisan 命令通过 commands() 方法注册。通常只在 Console 环境中注册。
use Acme\Courier\Console\Commands\ InstallCommand ;
use Acme\Courier\Console\Commands\ SyncCommand ;
public function boot () : void
{
if ( $this -> app -> runningInConsole ()) {
$this -> commands ([
InstallCommand :: class ,
SyncCommand :: class ,
]);
}
}
集成到 optimize 命令
如果包有自己的缓存,可通过 optimizes() 方法集成到 php artisan optimize 与 php artisan optimize:clear。
public function boot () : void
{
if ( $this -> app -> runningInConsole ()) {
$this -> optimizes (
optimize : 'courier:cache' ,
clear : 'courier:clear-cache' ,
);
}
}
向 about 命令追加信息
向 php artisan about 输出追加包信息,使用 AboutCommand::add()。
use Illuminate\Foundation\Console\ AboutCommand ;
public function boot () : void
{
AboutCommand :: add ( 'Courier Package' , fn () => [ 'Version' => '1.0.0' ]);
}
创建 Facade
使用 Facade 可以将服务容器绑定当作静态方法调用。
创建服务类
<? php
namespace Acme\Courier ;
class CourierManager
{
public function __construct (
protected array $config ,
) {}
public function send ( string $to , string $message ) : bool
{
// 消息发送逻辑
return true ;
}
public function track ( string $id ) : array
{
// 跟踪信息获取逻辑
return [ 'status' => 'delivered' ];
}
}
创建 Facade 类
继承 Illuminate\Support\Facades\Facade,在 getFacadeAccessor() 中返回服务容器绑定键。 <? php
namespace Acme\Courier\Facades ;
use Illuminate\Support\Facades\ Facade ;
/**
* @method static bool send(string $to, string $message)
* @method static array track(string $id)
*
* @see \Acme\Courier\CourierManager
*/
class Courier extends Facade
{
protected static function getFacadeAccessor () : string
{
return \Acme\Courier\ CourierManager :: class ;
}
}
在 Service Provider 中绑定
public function register () : void
{
$this -> app -> singleton ( \Acme\Courier\ CourierManager :: class , function ( $app ) {
return new \Acme\Courier\ CourierManager ( $app [ 'config' ][ 'courier' ]);
});
}
在 composer.json 中注册
"extra" : {
"laravel" : {
"providers" : [
"Acme \\ Courier \\ CourierServiceProvider"
],
"aliases" : {
"Courier" : "Acme \\ Courier \\ Facades \\ Courier"
}
}
}
在 Facade 的方法上加 PHPDoc @method 注解可获得 IDE 补全支持。
// 通过 Facade 调用服务
use Acme\Courier\Facades\ Courier ;
Courier :: send ( '[email protected] ' , '包裹已送达' );
$status = Courier :: track ( 'ABC-123' );
DeferrableProvider — 延迟加载的实现
只做服务容器绑定的 Provider,通过实现 DeferrableProvider 接口即可实现延迟加载。服务真正需要时才加载 Provider,可以提升应用性能。
<? php
namespace Acme\Courier ;
use Illuminate\Contracts\Support\ DeferrableProvider ;
use Illuminate\Support\ ServiceProvider ;
class CourierServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register () : void
{
$this -> app -> singleton ( CourierManager :: class , function ( $app ) {
return new CourierManager ( $app [ 'config' ][ 'courier' ]);
});
}
/**
* 返回该 Provider 提供的服务列表
*
* @return array < int , string>
*/
public function provides () : array
{
return [ CourierManager :: class ];
}
}
Laravel 会编译并保存延迟 Provider 所提供的服务清单。只有当 provides() 中列出的服务被解析时,Provider 才会加载。
需要注册资源(视图、路由、事件监听器等)的 Provider 请勿使用 DeferrableProvider。一旦被延迟加载,这些资源就会未被注册。
包的测试
要对包本身进行测试,请使用 Orchestra Testbench 。它可以让你像在普通 Laravel 应用中一样编写包的测试。
composer require --dev orchestra/testbench
在测试用例中覆写 getPackageProviders() 以注册包的 Service Provider。
<? php
namespace Acme\Courier\Tests ;
use Acme\Courier\ CourierServiceProvider ;
use Orchestra\Testbench\ TestCase as BaseTestCase ;
class TestCase extends BaseTestCase
{
/**
* 注册包的 Service Provider
*/
protected function getPackageProviders ( $app ) : array
{
return [
CourierServiceProvider :: class ,
];
}
/**
* 注册包的 Facade 别名
*/
protected function getPackageAliases ( $app ) : array
{
return [
'Courier' => \Acme\Courier\Facades\ Courier :: class ,
];
}
/**
* 测试用环境配置
*/
protected function defineEnvironment ( $app ) : void
{
$app [ 'config' ] -> set ( 'courier.api_key' , 'test-key' );
}
}
<? php
namespace Acme\Courier\Tests\Feature ;
use Acme\Courier\Facades\ Courier ;
use Acme\Courier\Tests\ TestCase ;
class CourierTest extends TestCase
{
public function test_can_send_message () : void
{
$result = Courier :: send ( '[email protected] ' , '测试消息' );
$this -> assertTrue ( $result );
}
}
发布到 Composer
将包发布到 Packagist 的最佳实践。
composer.json 的基本配置
{
"name" : "acme/courier" ,
"description" : "A Laravel courier package" ,
"type" : "library" ,
"license" : "MIT" ,
"require" : {
"php" : "^8.2" ,
"illuminate/support" : "^11.0||^12.0||^13.0"
},
"require-dev" : {
"orchestra/testbench" : "^9.0||^10.0" ,
"phpunit/phpunit" : "^11.0"
},
"autoload" : {
"psr-4" : {
"Acme \\ Courier \\ " : "src/"
}
},
"autoload-dev" : {
"psr-4" : {
"Acme \\ Courier \\ Tests \\ " : "tests/"
}
},
"extra" : {
"laravel" : {
"providers" : [
"Acme \\ Courier \\ CourierServiceProvider"
],
"aliases" : {
"Courier" : "Acme \\ Courier \\ Facades \\ Courier"
}
}
},
"minimum-stability" : "stable" ,
"prefer-stable" : true
}
依赖 illuminate/support 而非整个 illuminate/framework,可以只引入 Laravel 中你真正需要的组件。让包的依赖树保持精简。
目录结构示例
acme/courier/
├── config/
│ └── courier.php
├── database/
│ └── migrations/
│ └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│ └── zh_CN/
│ └── messages.php
├── resources/
│ └── views/
│ └── dashboard.blade.php
├── routes/
│ └── web.php
├── src/
│ ├── Console/
│ │ └── Commands/
│ │ └── InstallCommand.php
│ ├── Facades/
│ │ └── Courier.php
│ ├── Http/
│ │ └── Controllers/
│ │ └── TrackingController.php
│ ├── CourierManager.php
│ └── CourierServiceProvider.php
├── tests/
│ ├── Feature/
│ └── TestCase.php
├── composer.json
└── README.md
相关页面
Service Provider 了解 Service Provider 的 register 与 boot 方法,以及延迟 Provider 的详细内容。
版本兼容性管理 讲解应对 Laravel 与 PHP 主版本升级的策略,以及 GitHub Actions 测试矩阵配置。