laravel
5. 深入探讨
包开发

介绍

包是向 Laravel 添加功能的主要方式。包可以是各种各样的,比如像 Carbon (opens in a new tab) 这样处理日期的好方法,或者像 Spatie 的 Laravel Media Library (opens in a new tab) 这样允许您将文件与 Eloquent 模型相关联的包。

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

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

关于外观(Facades)的说明

在编写 Laravel 应用程序时,您使用契约(contracts)还是外观(facades)通常并不重要,因为两者在可测试性方面基本上提供了相同的级别。然而,在编写包时,您的包通常无法访问 Laravel 的所有测试助手。如果您希望能够像您的包安装在一个典型的 Laravel 应用程序中一样编写您的包测试,您可以使用 Orchestral Testbench (opens in a new tab) 包。

包发现

Laravel 应用程序的 bootstrap/providers.php 文件包含了应由 Laravel 加载的服务提供者的列表。然而,您可以在您的包的 composer.json 文件的 extra 部分中定义服务提供者,以便 Laravel 自动加载它,而不是要求用户手动将您的服务提供者添加到列表中。除了服务提供者,您还可以列出您希望注册的任何 外观(Facades)

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

一旦您的包已配置为可发现,当它被安装时,Laravel 将自动注册其服务提供者和外观(Facades),为您的包的用户创造一个方便的安装体验。

选择退出包发现

如果您是一个包的使用者,并且希望为一个包禁用包发现,您可以在您的应用程序的 composer.json 文件的 extra 部分中列出包的名称:

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

您可以在您的应用程序的 dont-discover 指令中使用 * 字符来禁用所有包的包发现:

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

服务提供者

服务提供者 是您的包和 Laravel 之间的连接点。服务提供者负责将事物绑定到 Laravel 的 服务容器 中,并告知 Laravel 在哪里加载包资源,如视图、配置和语言文件。

服务提供者扩展了 Illuminate\Support\ServiceProvider 类,并包含两个方法:registerboot。基础的 ServiceProvider 类位于 illuminate/support Composer 包中,您应该将其添加到您自己的包的依赖项中。要了解更多关于服务提供者的结构和目的,请查看 它们的文档

资源

配置

通常,您需要将您的包的配置文件发布到应用程序的 config 目录中。这将允许您的包的用户轻松地覆盖您的默认配置选项。为了允许您的配置文件被发布,从您的服务提供者的 boot 方法中调用 publishes 方法:

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

现在,当您的包的用户执行 Laravel 的 vendor:publish 命令时,您的文件将被复制到指定的发布位置。一旦您的配置已发布,其值可以像任何其他配置文件一样被访问:

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

[!WARNING]
您不应该在您的配置文件中定义闭包。当用户执行 config:cache Artisan 命令时,它们无法正确序列化。

默认包配置

您还可以将自己的包配置文件与应用程序发布的副本进行合并。这将允许您的用户仅在发布的配置文件副本中定义他们实际想要覆盖的选项。要合并配置文件值,请在服务提供程序的register方法中使用mergeConfigFrom方法。

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

/**
 * 注册任何应用程序服务。
 */
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}

[!WARNING]
此方法仅合并配置数组的第一层。如果您的用户部分定义了多维配置数组,则缺失的选项将不会被合并。

路由

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

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

迁移

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

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

语言文件

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

/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}

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

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

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

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

发布语言文件

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

/**
 * 引导任何包服务。
 */
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方法中添加以下内容:

/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}

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

Route::get('/dashboard', function () {
    return view('courier::dashboard');
});

覆盖包视图

当您使用loadViewsFrom方法时,Laravel 实际上为您的视图注册了两个位置:应用程序的resources/views/vendor目录和您在loadViewsFrom调用中指定的目录。因此,以courier包为例,Laravel 将首先检查开发人员是否在resources/views/vendor/courier目录中放置了视图的自定义版本。然后,如果视图未被自定义,Laravel 将在您在loadViewsFrom调用中指定的包视图目录中进行搜索。这使得包用户可以轻松地自定义/覆盖您的包的视图。

发布视图

如果您希望将您的视图发布到应用程序的 resources/views/vendor 目录中,您可以使用服务提供者的 publishes 方法。publishes 方法接受一个包含包视图路径及其期望发布位置的数组:

/**
 * 引导包的服务。
 */
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 方法中注册您的组件:

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

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

一旦您的组件已注册,就可以使用其标签别名进行渲染:

<x-package-alert/>

自动加载包组件

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

use Illuminate\Support\Facades\Blade;

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

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

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

Blade 将通过将组件名称转换为帕斯卡命名法自动检测与此组件关联的类。也支持使用“点”符号表示子目录。

匿名组件

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

<x-courier::alert />

“关于”Artisan 命令

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

use Illuminate\Foundation\Console\AboutCommand;

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

命令

要将您的包的 Artisan 命令与 Laravel 注册,您可以使用 commands 方法。此方法期望一个命令类名数组。一旦命令已注册,您就可以使用 Artisan CLI 执行它们:

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

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

公共资产

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

/**
 * 引导任何包服务。
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../public' => public_path('vendor/courier'),
    ], 'public');
}

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

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

发布文件组

您可能希望分别发布包资产和资源的组。例如,您可能希望允许您的用户发布您的包的配置文件,而不必强制发布您的包的资产。您可以通过在从包的服务提供者调用 publishes 方法时对它们进行“标记”来实现此目的。例如,让我们在包的服务提供者的 boot 方法中使用标记为 courier 包(courier-configcourier-migrations)定义两个发布组:

/**
 * 引导任何包服务。
 */
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 命令时引用其标记来分别发布这些组:

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