Skip to content

包开发 ​

简介 ​

包是向 Laravel 添加功能的主要方式。包可以是任何东西,从像 Carbon 这样处理日期的好工具,到像 Spatie 的 Laravel Media Library 这样允许你将文件与 Eloquent 模型关联的包。

有不同类型的包。有些包是独立的,这意味着它们可以与任何 PHP 框架一起使用。Carbon 和 Pest 就是独立包的例子。任何这些包都可以通过在 composer.json 文件中引入它们来与 Laravel 一起使用。

另一方面,其他包专门用于 Laravel。这些包可能具有专门用于增强 Laravel 应用程序的路由、控制器、视图和配置。本指南主要涵盖那些特定于 Laravel 的包的开发。

创建包 ​

开始构建新 Laravel 包的最简单方式是使用官方 Laravel 包骨架。该骨架提供了构建 Laravel 包所需的一切,包括 service provider、通过 Pest 进行测试、通过 Larastan 进行静态分析、通过 Pint 进行代码格式化,以及用于端到端包开发的 workbench 应用程序。你可以使用 Laravel 安装器 CLI 的 package 命令创建新包:

shell
laravel package my-package

交互式配置脚本会针对你的包定制该骨架,设置命名空间、service provider,并仅包含你需要的功能,例如配置文件、路由、视图、翻译、migration、资源、命令和 facade。

关于 Facades 的说明 ​

编写 Laravel 应用程序时,使用契约还是 facade 通常无关紧要,因为两者提供基本相同的可测试性级别。但是,在编写包时,你的包通常无法访问所有 Laravel 的测试辅助函数。如果你希望能够像包安装在典型 Laravel 应用程序中一样编写包测试,可以使用 Orchestral Testbench 包。

包发现 ​

Laravel 应用程序的 bootstrap/providers.php 文件包含 Laravel 应该加载的服务提供者列表。但是,你可以在包的 composer.json 文件的 extra 部分中定义提供者,而不是要求用户手动将你的服务提供者添加到列表中,这样 Laravel 会自动加载它。除了服务提供者,你还可以列出任何你想要注册的 facade:

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

一旦你的包配置了发现功能,Laravel 将在安装时自动注册其服务提供者和 facade,为你的包用户创造便捷的安装体验。

退出包发现 ​

如果你是包的使用者,想要禁用某个包的包发现功能,可以在应用程序的 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
/**
 * 引导任何包服务。
 */
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
/**
 * 注册任何包服务。
 */
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}

WARNING

此方法仅合并配置数组的第一级。如果你的用户部分定义了多维配置数组,缺少的选项将不会被合并。

路由 ​

如果你的包包含路由,可以使用 loadRoutesFrom 方法加载它们。此方法将自动确定应用程序的路由是否已缓存,如果路由已缓存则不会加载你的路由文件:

php
/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}

迁移 ​

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

php
/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}

语言文件 ​

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

php
/**
 * 引导任何包服务。
 */
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
/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}

发布语言文件 ​

如果你想将包的语言文件发布到应用程序的 lang/vendor 目录,可以使用服务提供者的 publishes 方法。publishes 方法接受包路径及其期望的发布位置的数组。例如,要发布 courier 包的语言文件,你可以执行以下操作:

php
/**
 * 引导任何包服务。
 */
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
/**
 * 引导任何包服务。
 */
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
/**
 * 引导包服务。
 */
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;

/**
 * 引导包服务。
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}

注册组件后,可以使用其标签别名来渲染它:

blade
<x-package-alert/>

自动加载包组件 ​

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

php
use Illuminate\Support\Facades\Blade;

/**
 * 引导包服务。
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这将允许使用 package-name:: 语法通过供应商命名空间使用包组件:

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

Blade 将通过 pascal-casing 组件名称来自动检测链接到此组件的类。也支持使用「点」符号的子目录。

匿名组件 ​

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

blade
<x-courier::alert />

"About" Artisan 命令 ​

Laravel 内置的 about Artisan 命令提供了应用程序环境和配置的概要。包可以通过 AboutCommand 类向此命令的输出推送额外信息。通常,可以从包服务提供者的 boot 方法中添加此信息:

php
use Illuminate\Foundation\Console\AboutCommand;

/**
 * 引导任何包服务。
 */
public function boot(): void
{
    AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}

命令 ​

要向 Laravel 注册包的 Artisan 命令,可以使用 commands 方法。此方法接受命令类名的数组。注册命令后,你可以使用 Artisan CLI 执行它们:

php
use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;

/**
 * 引导任何包服务。
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            NetworkCommand::class,
        ]);
    }
}

优化命令 ​

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

php
/**
 * 引导任何包服务。
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'package:optimize',
            clear: 'package:clear-optimizations',
        );
    }
}

重载命令 ​

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

php
/**
 * 引导任何包服务。
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->reloads('package:reload');
    }
}

公共资源 ​

你的包可能有 JavaScript、CSS 和图片等资源。要将这些资源发布到应用程序的 public 目录,请使用服务提供者的 publishes 方法。在此示例中,我们还将添加一个 public 资源组标签,可用于轻松发布相关资源组:

php
/**
 * 引导任何包服务。
 */
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
/**
 * 引导任何包服务。
 */
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"