Skip to content
全部文档

包开发

简介

包是向 Laravel 添加功能的主要方式。包可以是像 Carbon 这样出色的日期处理工具,也可以是像 Spatie 的 Laravel Media Library 这样允许将文件与 Eloquent 模型关联的包。

包有不同类型。有些是独立包,可与任意 PHP 框架一起使用。Carbon 和 Pest 就是独立包的例子。只要在 composer.json 中引入,这些包都可以在 Laravel 中使用。

另一方面,有些包专门面向 Laravel。这类包可能包含专门用于增强 Laravel 应用的路由、控制器、视图和配置。本指南主要介绍这类 Laravel 专用包的开发。

关于 Facades 的说明

编写 Laravel 应用时,使用契约还是 facade 通常无关紧要,因为两者的可测试性基本相当。但在编写包时,包通常无法使用 Laravel 的全部测试辅助工具。若希望像包安装在典型 Laravel 应用中那样编写包测试,可以使用 Orchestral Testbench 包。

包发现

Laravel 应用的 bootstrap/providers.php 文件包含应由 Laravel 加载的服务提供者列表。不过,无需要求用户手动将你的服务提供者加入该列表,你可以在包的 composer.jsonextra 部分定义提供者,以便 Laravel 自动加载。除服务提供者外,你还可以列出希望注册的任意 facades

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

一旦为包配置了发现机制,Laravel 在安装该包时会自动注册其服务提供者和 facades,从而为包的用户提供便捷的安装体验。

退出包发现

若你是包的使用者,希望禁用某个包的包发现,可在应用的 composer.jsonextra 部分列出该包名:

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

你可以在应用的 dont-discover 指令中使用 * 字符,以禁用所有包的包发现:

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

服务提供者

服务提供者是包与 Laravel 之间的连接点。服务提供者负责将内容绑定到 Laravel 的服务容器,并告知 Laravel 在何处加载视图、配置和语言文件等包资源。

服务提供者继承 Illuminate\Support\ServiceProvider 类,并包含两个方法:registerboot。基础 ServiceProvider 类位于 illuminate/support Composer 包中,你应将其加入自己包的依赖。要了解服务提供者的结构与用途,请参阅其文档

资源

配置

通常,你需要将包的配置文件发布到应用的 config 目录。这样包的用户就能轻松覆盖默认配置选项。要允许发布配置文件,请在服务提供者的 boot 方法中调用 publishes 方法:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}

现在,当包的用户执行 Laravel 的 vendor:publish 命令时,你的文件会被复制到指定的发布位置。配置发布后,即可像其他配置文件一样访问其值:

php
$value = config('courier.option');

WARNING

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

默认包配置

你也可以将自己的包配置文件与应用中已发布的副本合并。这样用户只需在已发布的配置副本中定义真正想覆盖的选项。要合并配置文件的值,请在服务提供者的 register 方法中使用 mergeConfigFrom 方法。

mergeConfigFrom 方法的第一个参数是包配置文件的路径,第二个参数是应用中该配置文件副本的名称:

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

WARNING

该方法仅合并配置数组的第一层。若用户只部分定义了多维配置数组,缺失的选项将不会被合并。

路由

若包包含路由,可使用 loadRoutesFrom 方法加载它们。该方法会自动判断应用的路由是否已缓存;若路由已缓存,则不会加载你的路由文件:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}

迁移

若包包含数据库迁移,可使用 publishesMigrations 方法告知 Laravel 给定目录或文件包含迁移。Laravel 发布迁移时,会自动更新文件名中的时间戳,以反映当前日期和时间:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}

语言文件

若包包含语言文件,可使用 loadTranslationsFrom 方法告知 Laravel 如何加载它们。例如,若包名为 courier,应在服务提供者的 boot 方法中加入以下内容:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}

包的翻译条目使用 package::file.line 语法约定引用。因此,你可以像这样从 messages 文件加载 courier 包的 welcome 条目:

php
echo trans('courier::messages.welcome');

你可以使用 loadJsonTranslationsFrom 方法为包注册 JSON 翻译文件。该方法接受包含包 JSON 翻译文件的目录路径:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}

发布语言文件

若希望将包的语言文件发布到应用的 lang/vendor 目录,可使用服务提供者的 publishes 方法。publishes 方法接受包路径及其目标发布位置的数组。例如,要发布 courier 包的语言文件,可以这样做:

php
/**
 * 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 方法中加入以下内容:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}

包视图使用 package::view 语法约定引用。因此,一旦在服务提供者中注册了视图路径,就可以像这样加载 courier 包的 dashboard 视图:

php
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 方法接受包视图路径及其目标发布位置的数组:

php
/**
 * 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 方法中注册组件:

php
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);
}

组件注册后,即可通过其标签别名渲染:

blade
<x-package-alert/>

自动加载包组件

或者,你可以使用 componentNamespace 方法按约定自动加载组件类。例如,Nightshade 包可能有位于 Nightshade\Views\Components 命名空间中的 CalendarColorPicker 组件:

php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这样即可通过厂商命名空间,使用 package-name:: 语法引用包组件:

blade
<x-nightshade::calendar />
<x-nightshade::color-picker />

Blade 会通过将组件名转为帕斯卡命名(PascalCase)自动检测与该组件关联的类。子目录也支持使用「点」表示法。

匿名组件

若包包含匿名组件,它们必须放在包的「views」目录(由 loadViewsFrom 方法 指定)下的 components 目录中。然后,可通过在组件名前加上包的视图命名空间来渲染它们:

blade
<x-courier::alert />

「About」Artisan 命令

Laravel 内置的 about Artisan 命令会提供应用环境与配置的概要。包可通过 AboutCommand 类向该命令的输出追加额外信息。通常可在包服务提供者的 boot 方法中添加这些信息:

php
use Illuminate\Foundation\Console\AboutCommand;

/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}

命令

要将包的 Artisan 命令注册到 Laravel,可使用 commands 方法。该方法期望接收命令类名数组。命令注册后,即可通过 Artisan CLI 执行它们:

php
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,
        ]);
    }
}

Optimize 命令

Laravel 的 optimize 命令 会缓存应用的配置、事件、路由和视图。使用 optimizes 方法,你可以注册在执行 optimizeoptimize:clear 命令时应调用的包自有 Artisan 命令:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'package:optimize',
            clear: 'package:clear-optimizations',
        );
    }
}

Reload 命令

Laravel 的 reload 命令 会终止正在运行的服务,以便系统进程监控器自动重启它们。使用 reloads 方法,你可以注册在执行 reload 命令时应调用的包自有 Artisan 命令:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->reloads('package:reload');
    }
}

公共资源

包可能包含 JavaScript、CSS 和图片等资源。要将这些资源发布到应用的 public 目录,请使用服务提供者的 publishes 方法。本例中我们还会添加 public 资源组标签,以便轻松发布相关资源组:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../public' => public_path('vendor/courier'),
    ], 'public');
}

现在,当包的用户执行 vendor:publish 命令时,资源将被复制到指定的发布位置。由于用户通常需要在每次包更新时覆盖这些资源,他们可以使用 --force 标志:

shell
php artisan vendor:publish --tag=public --force

发布文件组

你可能希望分别发布各组包资源与文件。例如,允许用户发布包的配置文件,而不必强制发布包的静态资源。可以在包服务提供者中调用 publishes 方法时通过「打标签」实现。例如,我们在包服务提供者的 boot 方法中用标签为 courier 包定义两个发布组(courier-configcourier-migrations):

php
/**
 * 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 命令时通过引用标签分别发布这些组:

shell
php artisan vendor:publish --tag=courier-config

用户也可以使用 --provider 标志,发布由包服务提供者定义的全部可发布文件:

shell
php artisan vendor:publish --provider="Your\Package\ServiceProvider"