扩展包开发

简介

扩展包是向 Laravel 添加功能的主要方式。扩展包可能是用很好的方式处理日期的 Carbon,或者像 Behat 这样完整的 BDD 测试框架。

当然,扩展包有很多不同类型的。一些扩展包是独立的,可以和任何 PHP 框架一起使用。Carbon 和 Behat 就是独立扩展包的例子。任何这些扩展包都可以添加到 composer.json 文件和 Laravel 一起使用。

另一方面,其它扩展包专供 Laravel 使用。这些扩展包可能有路由,控制器,视图和配置等来增强 Laravel 应用。本文档主要介绍 Laravel 使用的扩展包开发。

Facades 注意事项

编写 Laravel 应用时,使用 Contracts 或 Facades 无关紧要,因为两者都提供了基本相同的可测试性。但是,编写扩展包时,扩展包通常无法访问 Laravel 的所有测试辅助函数。如果希望能和在通常的 Laravel 应用中一样编写扩展包测试,可以使用 Orchestral Testbench 扩展包。

扩展包发现

在 Laravel 应用的 config/app.php 配置文件中,providers 选项定义了 Laravel 应该加载的服务提供者列表。当有人安装您的扩展包时,通常要将服务提供者添加到此列表中。可以在扩展包的 composer.json 文件的 extra 部分定义服务提供者,而不是要求用户手动将其添加到列表中。除了服务提供者之外,还可以列出要注册的任何 Facades

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

配置扩展包发现之后,Laravel 会在扩展包安装后自动注册其服务提供者和 Facades,为扩展包用户提供方便的安装体验。

关闭扩展包发现

如果您是扩展包的使用者并想禁用扩展包发现,可以将扩展包名字列在应用的 composer.jsonextra 部分:

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

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

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

服务提供者

服务提供者 是扩展包和 Laravel 之间的连接点。服务提供者负责将东西绑定到 Laravel 的 服务容器 并通知 Laravel 在哪里加载扩展包资源,例如视图,配置和本地化文件。

服务提供者继承自 Illuminate\Support\ServiceProvider 类并提供两个方法:registerbootServiceProvider 基类位于 Composer 扩展包的 illuminate/support 中,应该将其添加到自己的扩展包依赖。有关服务提供者的结构和用途的更多信息,请查看 服务提供者的文档

资源

配置

通常情况下,需要将扩展包的配置文件发布到应用自己的 config 目录。这样扩展包用户就可以轻松重写默认的配置选项。要允许配置文件被发布,在服务提供者的 boot 方法中调用 publishes 方法:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->publishes([
        __DIR__.'/path/to/config/courier.php' => config_path('courier.php'),
    ]);
}

现在,当扩展包用户运行 vendor:publish 命令时,配置文件就会被拷贝到指定的发布位置。当然,配置发布后,可以像任何其它配置文件一样访问其值:

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

不应该在配置文件中定义闭包。因为用户运行 Artisan 命令 config:cache 时,它们不能被正确序列化。

扩展包默认配置

还可以将扩展包配置文件与发布到应用的配置文件副本合并。允许用户只定义他们实际想在副本中重写的配置。要合并配置,可以在服务提供者的 register 方法中使用 mergeConfigFrom 方法:

/**
 * 注册容器绑定
 *
 * @return void
 */
public function register()
{
    $this->mergeConfigFrom(
        __DIR__.'/path/to/config/courier.php', 'courier'
    );
}

此方法只会合并配置数组的第一级。如果用户定义了多维配置数组,其它选项不会被合并。

路由

如果扩展包包含路由,可以使用 loadRoutesFrom 犯法加载它们。此方法会自动判断应用的路由是否已缓存,如果已缓存,将不会加载路由文件:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadRoutesFrom(__DIR__.'/routes.php');
}

数据库迁移

如果扩展包包含 数据库迁移,可以使用 loadMigrationsFrom 方法通知 Laravel 怎样加载它们。loadMigrationsFrom 方法接收扩展包的迁移路径作为其唯一参数:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
}

扩展包迁移注册后,它们会在执行 php artisan migrate 命令时自动运行迁移。

语言文件

如果扩展包包含 语言文件,可以使用 loadTranslationsFrom 方法通知 Laravel 怎样加载它们。例如,如果扩展包被命名为 courier,应该将如下内容添加到服务提供者的 boot 方法:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
}

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

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

发布语言文件

如果要将扩展包语言文件发布到应用的 resources/lang/vendor 目录,可以使用服务提供者的 publishes 方法。publishes 方法接收一个扩展包路径和要发布位置的数组。例如,要为 courier 扩展包发布语言文件,可以这样做:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');

    $this->publishes([
        __DIR__.'/path/to/translations' => resource_path('lang/vendor/courier'),
    ]);
}

现在,当用户执行 Laravel 的 Artisan 命令 vendor:publish 时,扩展包语言文件会被发布到指定的发布位置。

视图

要在 Laravel 中注册扩展包 视图,需要告诉 Laravel 视图位于哪里。可以使用服务提供者的 loadViewsFrom 方法完成此操作。loadViewsFrom 方法接收两个参数:视图模板的路径和扩展包名称。例如,如果扩展包名为 courier,可以添加下列内容到服务提供者的 boot 方法:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
}

扩展包视图使用 package::view 语法约定进行引用。因此,在服务提供者中注册视图路径后,可以像这样加载 courier 扩展包的 admin 视图:

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

重写扩展包视图

当使用 loadViewsFrom 方法时,Laravel 实际上为视图注册了两个位置:应用的 resources/views/vendor 目录和您指定的目录。因此,以 courier 为例,Laravel 首先会检查服务提供者是否在 resources/views/vendor/courier 中提供了自定义版本视图。然后,如果视图没有自定义,Laravel 会搜索调用 loadViewsFrom 时指定的扩展包视图目录。这使得扩展包用户可以轻松自定义/重写扩展包视图。

发布视图

如果要将视图发布到应用的 resources/views/vendor 目录使用,可以使用服务提供者的 publishes 方法。publishes 方法接收一个扩展包视图路径和要发布位置的数组:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');

    $this->publishes([
        __DIR__.'/path/to/views' => resource_path('views/vendor/courier'),
    ]);
}

现在,当扩展包用户执行 Laravel 的 Artisan 命令 vendor:publish 时,扩展包视图会被拷贝到指定的发布位置。

命令

要在 Laravel 中注册扩展包的 Artisan 命令,可以使用 commands 方法。此方法接收一个命令类名数组。命令注册后,就可以使用 Artisan CLI 执行它们了:

/**
 * 启动应用服务
 *
 * @return void
 */
public function boot()
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            FooCommand::class,
            BarCommand::class,
        ]);
    }
}

公共资源

扩展包可能还有资源,如 JavaScript,CSS 和图片。要发布这些资源到应用的 public 目录,可以使用服务提供者的 publishes 方法。在此示例中,我们还会添加一个 public 资源组标签,用于发布对应组关联的资源:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->publishes([
        __DIR__.'/path/to/assets' => public_path('vendor/courier'),
    ], 'public');
}

现在,当扩展包用户执行 vendor:publish 命令时,资源文件会被拷贝到指定的发布位置。由于在扩展包每次更新后通常要覆盖资源文件,因此可以使用 --force 标识:

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

发布一组文件

可能想要单独发布一组扩展包静态资源或资源文件。举个例子,可能希望允许用户发布扩展包的配置文件而不用被强制发布扩展包静态资源。可以通过在扩展包的服务提供者调用 publishes 方法时为它们「打标签」完成此操作。例如,我们在扩展包的服务提供者中使用标签定义两个发布组:

/**
 * 注册后启动服务
 *
 * @return void
 */
public function boot()
{
    $this->publishes([
        __DIR__.'/../config/package.php' => config_path('package.php')
    ], 'config');

    $this->publishes([
        __DIR__.'/../database/migrations/' => database_path('migrations')
    ], 'migrations');
}

现在用户就可以在执行 vendor:publish 命令时指定标签单独发布这些资源组了:

php artisan vendor:publish --tag=config