简介 扩展包是向 Laravel 添加功能的主要方式。扩展包可以包含很多有用的功能,比如时间处理扩展包 Carbon,亦或者是提供完整 BDD 测试框架的扩展包 Behat。 扩展包有很多的类型。有些扩展包是独立的,这意味着它们可以和任何 PHP 框架一起使用。Carbon 和 Behat 就是这样的独立扩展包。要在 Laravel 中使用这些扩展包只需要在 composer.json 文件中引入它们即可。 另外,有些扩展包只能在 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.json 文件中的 extra 部分列出这个扩展包: ``` "extra": { "laravel": { "dont-discover": [ "barryvdh/laravel-debugbar" ] } }, ``` 你也可以在应用的 dont-discover 指令中使用 * 字符,禁用所有扩展包的发现功能: ``` "extra": { "laravel": { "dont-discover": [ "*" ] } }, ``` 服务提供者 服务提供者 将你的扩展包和 Laravel 联系在一起。服务提供者负责将事物绑定到 Laravel 服务容器 中,并告诉 Laravel 从哪里加载扩展包的资源文件,例如视图、配置文件、语言包等。 服务提供者继承了 Illuminate\Support\ServiceProvider 类,并包含两个方法: register 和 boot。基类 ServiceProvider 位于 Composer 扩展包的 illuminate/support 中,你必须将它添加到你的扩展包依赖项中。要了解有关服务提供者的结构和用途的更多信息,请查阅 它的文档. 资源文件 配置 通常,你需要将扩展包的配置文件发布到应用本身的 config 目录中。这样使用扩展包的用户就可以轻松的重写默认配置项。要发布配置文件,只需要在服务提供者的 boot 方法中调用 publishes 方法: ``` /** * 启动应用服务 * * @return void */ public function boot() { $this->publishes([ __DIR__.'/path/to/config/courier.php' => config_path('courier.php'), ]); } ``` 现在,当扩展包的用户执行 Laravel 的 vendor:publish 命令,扩展包文件将被复制到指定的目录中,发布配置后,就可以像其它配置一样被访问: ``` $value = config('courier.option'); ``` 注意:你不应该在配置文件中定义闭包函数。当用户执行 config:cache Artisan 命令时,配置文件将不能被正确的序列化。 扩展包默认配置 你可以将扩展包默认配置和应用的已发布副本配置合并在一起。这样扩展包用户就可以在副本配置文件中定义他们想要覆盖的配置选项。想要合并配置,只需要在服务提供者的 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 命令时它们就会被自动执行。你不需要将它们导入到应用的 database/migrations 目录中。 Translations 如果你的扩展包中包含 语言包文件 ,你需要使用 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 的 vendor:publish Artisan 命令,语言包将会被发布到指定的目录中。 视图 想要在 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 的 vendor:publish Artisan 命令,扩展包视图将会被发布到指定的目录中。 视图组件 如果您的软件包包含视图组件,则可以使用 loadViewComponentsAs 方法通知 Laravel 如何加载它们。 loadViewComponentsAs 方法接受两个参数:视图组件的标签前缀和视图组件类组成的数组。 例如,如果包的前缀是 courier,并且具有 Alert 和 Button 视图组件,则可以将以下内容添加到服务提供者的 boot 方法中: ``` /** * 启动应用服务 * * @return void */ public function boot() { $this->loadViewComponentsAs('courier', [ Alert::class, Button::class, ]); } ``` 一旦在服务提供者中注册了视图组件,就可以直接在视图中引用它们,如下所示: ``` <x-courier-alert /> <x-courier-button /> ``` 匿名组件 如果您的程序包包含匿名组件,则必须将它们放置在您程序包的 views 目录的 components 文件夹中(由 loadViewsFrom 指定)。 然后,可以通过在组件名称的前面加上包的视图命名空间来渲染它们: ``` <x-courier::alert /> ``` 命令 想要在 Laravel 中注册扩展包的 Artisan 命令,需要使用 commands 方法。此方法接收一个命令类的数组,一旦这些命令被注册成功,你可以使用 Artisan 命令行 执行他们: ``` /** * 启动应用服务 * * @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 方法时对他们打上「标签」。例如,让我们使用扩展包服务提供者中的 boot 方法来定义两个发布群组: ``` /** * 启动应用服务 * * @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 ```