编程

Laravel扩展包开发

865 2021-12-26 10:39:04

扩展包是Laravel添加功能的主要方式。扩展可以是任何形式,比如处理日期的Carbon包或者允许你将文件和Eloquent模型关联的Spatie's Laravel Media Library 库。

扩展包有不同的形式。有些包是独立的,可以和任何的PHP框架配合使用。Carbon 及 PHPUnit 就是这样的例子。这样的包可以通过composer.json文件来引入被Laravel使用.

此外还有一些包是专门用于Laravel的,这些包可能包含路由(routes), 控制器(controllers), 视图(Views)和配置用以加强Laravel应用。本教程主要讲的就是这种Laravel专用的扩展包的开发。

关于门面Facades

当编写Laravel应用时,一般来说使用contracts还是用facades其实无关紧要,因为两者提供的同等的测试能力。不过,当编写扩展包时,你的应用包通常没有办法访问Laravel的测试辅助函数helper。如果你想要像Laravel内那样编写扩展包的测试,你可以使用 Orchestral Testbench 扩展包。

扩展包发现

在Laravel应用的config/app.php配置文件中,providers 选项定义了一系列Laravel应该加载的服务提供者。当有人安装了你的扩展包,你自然希望你的service provide被包含在上述配置文件里面。你可以在你扩展包的composer.json文件中, 将你的provider定义在extra区域中,让laravel自动发现你的包,而不必手动将其手动加入到配置中。除了service provider, 你也可以列出任何你想要注册的门面facades :

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

一旦你的扩展包配置了发现包,Laravel 在安装包时自动注册服务提供者Service Provider和门面Facade, 为扩展包的用户创造一个便利的安装体验。

退出包发现

如果你是扩展包的消费者,你想要禁用扩展包发现, 你可以在应用的composer.json文件中extra区域将对应的包名列出:

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

你也可以使用的 dont-discover 指令中使用 * 通配符禁用包发现功能:

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

服务提供者

服务提供是你的扩展包和laravel之间的连接点。服务提供者负责将服务绑定到laravel服务容器中并通知laravel到哪里去加载包资源,比如视图、配置和本地文件。

服务提供者继承扩展了 Illuminate\Support\ServiceProvider 类,包含 register 和 boot 两个方法。 ServiceProvider 基类位于 illuminate/support 的 Composer 包中, 你应该将其添加到你的包依赖中。

更多关于服务提供者结构和信息,可查阅相关文档。

资源

配置

通常你需要将包的配置文件发布到应用配置目录。这样可以让你的扩展包用户可以修改你的默认配置项。可以在服务提供者的boot方法中调用publishes方法来允许发布配置文件。

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

现在,你的包用户执行 Laravel 的 vendor:publish 命令的时候,你的文件会被复制到指定的发布位置。一旦你的配置发布成功,就可以像其他配置文件一样获取配置值了:

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

不要再配置文件中定义闭包。因为用户执行 `config:cache` Artisan 命令时, 会导致系列化失败。

默认包配置

你也可以将你包内的配置文件和发布出去的应用配置合并。这样可以让用户重写发布的陪只有文件,只定义他们需要配置选项。要合并配置文件值,在你的服务提供者 register 方法中使用 mergeConfigFrom 方法。mergeConfigFrom 方法接受一个到你包中配置文件的路径作为第一参数,以及发布的应用配置文件作为第二个参数:

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

该方法只会合并配置数组的第一级。如果您的用户部分定义了多维配置数组,则缺失的选项不会被合并。

路由

如果你的包中包含路由,你可以使用 loadRoutesFrom 方法加载。该方法自动应用路由是否已缓存,如果已缓存就不会再加载你的路由文件:

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

数据迁移

如果你的包中包含数据迁移,你可以使用 loadMigrationsFrom 方法通知 Laravel 加载。该方法接收你的包的 migration 路径作为唯一参数:

/**
 * Bootstrap any package services.
 *
 * @return void
 */
public function boot()
{
    $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}

一旦您的包的迁移被注册,在你执行 php artisan migrate 时,会自动运行该迁移。你不用将其导出到应用的 database/migrations 目录。

语言包

如果你包中包含语言包文件,你可以使用 loadTranslationsFrom 方法通知 laravel 加载他们。比如你包名为 courier,你应该将其添加到服务容器的 boot 方法中。

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

包中翻译使用 package::file.line 语法转换。所以,你可以像这样从 courier 包 message 文件中加载 welcome 语句:

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

发布语言文件

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

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

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

现在,包用户就可以执行laravel的vendor:publish命令将语言包发布到指定发布位置。

视图

要将你包中的视图注册到Laravel中,你需要告诉Laravel视图放在哪里。你可以使用服务提供者的loadViewsFrom 方法。 该方法接收两个参数:你视图文件模板的路径以及你的包名。比如,如果你的包名叫courier, 你可以添加以下内容到你服务提供者的boot方法:

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

可以使用package::view 语法转换引用扩展包视图。因此,当你在服务提供者中注册了视图之后,你可以这样加载视图包:

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 方法, 此方法接收一个数组包括包中的视图路径和它想要发布的位置:

/**
 * Bootstrap the package services.
 *
 * @return void
 */
public function boot()
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

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

现在,当你的包用户执行  Laravel 的 vendor:publish 命令,你包中视图将会被复制到指定的发布位置。

视图组件

如果你包中包含视图组件,你可以使用 loadViewComponentsAs 方法,通知 Laravel 加载它们。loadViewComponents 方法接收两个参数,你视图组件的标签前缀和视图组件类名数组。比如,你的包前缀是 courier, 同时有 Alert 和 Butto n视图组件,你可以将以下内容添加到服务提供者的 boot 方法:

use Courier\Components\Alert;
use Courier\Components\Button;

/**
 * Bootstrap any package services.
 *
 * @return void
 */
public function boot()
{
    $this->loadViewComponentsAs('courier', [
        Alert::class,
        Button::class,
    ]);
}

一旦你注册了服务提供者中的视图组件,你可以在视图中这样引用它们:

<x-courier-alert />

<x-courier-button />

匿名组件

如果你包中包含的匿名组件,如果你包中包含匿名组件,必须将其放在包中视图(views)目录之下的组件(components)目录(由 loadViewsFrom 指定)。因此,你可以通过使用包的命名空间作为组件前缀名渲染:

<x-courier::alert />

命令

注册你包中的 Artiasan 命令, 你可以使用 commands 方法。此方法接收一个命令类数组。当命令被注册后,你可以在Artisan CLI 命令行中执行:

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

/**
 * Bootstrap any package services.
 *
 * @return void
 */
public function boot()
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            NetworkCommand::class,
        ]);
    }
}

公共资源

你包中可能有JavaScript, CSS和图片。要将这些资源发布到应用的public目录,使用服务提供者 publishes 方法。下例,我们还会添加一个public 的资源标签,使之更容易发布public分组中的相关资源:

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

现在,当用户执行vendor:publish命令,你的资源包会被复制到指定的public位置中。因为每次更新包时都需要重写资源,你可以使用--force 标签:

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

发布文件分组

你可能想要分别发布扩展包分组中的 assets 和 resources. 举例来说,你想要让你的用户可以发布包中配置文件而不想发布包中 assets 资源。你可以在包中服务提供者调用 publishes 方法时,通过“tagging"标注它们。 比如,我们可以在包中的服务提供者的boot方法中使用标签来定义 courier 包的两个 publish 分组(courier-config 和 courier-migrations):

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

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

现在用户可以在执行 vendor:publish 命令时,通过分别引用它们的tag标签来发布: 

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