跳转到主要内容

什么是包

在 Laravel 中,包是通过 Composer 分发、用于向应用添加功能的组件。包大致分为两类:
  • 独立包 — 不依赖 Laravel 的通用 PHP 库(如 Carbon、Pest)
  • Laravel 包 — 包含路由、Controller、视图、配置等与 Laravel 集成的功能
本指南聚焦于后者,即 Laravel 专用包的开发。包开发需要深入理解 Service Provider、Facade、配置发布等 Laravel 内部结构。
为包编写测试时,请使用 Orchestra Testbench。它可以让你像编写普通 Laravel 应用测试一样编写包的测试。

包的自动发现

Laravel 在安装包时会读取 composer.jsonextra.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,包含 registerboot 两个方法。
<?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 optimizephp 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 可以将服务容器绑定当作静态方法调用。
1

创建服务类

<?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'];
    }
}
2

创建 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;
    }
}
3

在 Service Provider 中绑定

public function register(): void
{
    $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) {
        return new \Acme\Courier\CourierManager($app['config']['courier']);
    });
}
4

在 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 的 registerboot 方法,以及延迟 Provider 的详细内容。

版本兼容性管理

讲解应对 Laravel 与 PHP 主版本升级的策略,以及 GitHub Actions 测试矩阵配置。
最后修改于 2026年7月13日