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.json 的 extra 部分定义提供者,以便 Laravel 自动加载。除服务提供者外,你还可以列出希望注册的任意 facades:

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

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

退出包发现 ​

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

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

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

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

服务提供者 ​

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

服务提供者继承 Illuminate\Support\ServiceProvider 类,并包含两个方法:register 和 boot。基础 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 命名空间中的 Calendar 和 ColorPicker 组件:

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 方法,你可以注册在执行 optimize 与 optimize: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-config 与 courier-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"