套件開發

簡介

套件是用來給 Laravel 新增功能的主要方法。套件可以是任何東西,有像 Carbon 這樣可以方便處理日期的套件、或者是像 Spatie 的 Laravel Media Library 這樣用來處理與 Eloquent Model 關聯檔案的套件。

套件也有一些不同的類型。有的套件是獨立(Stand-alone)套件,這些套件在任何的 PHP 框架上都可使用。舉例來說,Carbon 與 PHPUnit 就是獨立套件。只要在 composer.json 檔案中加上這些套件,就可以在 Laravel 中使用。

另一方面,有的套件是特別為了供 Laravel 使用而設計的。這些套件可能會有 Route、Controller、View、設定檔等等用來增強 Laravel 程式的功能。本篇指南主要就是在討論有關開發這些專為 Laravel 設計的套件。

有關 Facade 的注意事項

在撰寫 Laravel 專案時,要使用 Contract 還是 Facade,一般來說沒什麼差別,因為兩者的可測試性都是相同的。不過,在開發套件的時候,我們要開發的套件可能無法存取所有的 Laravel 測試輔助函式。若想在測試套件是能像在一般的 Laravel 專案一樣測試,可使用 Orchestral Testbench 套件。

Package Discovery

在 Laravel 專案的 config/app.php 設定中,providers 選項列出了 Laravel 要載入的 Service Provider。當有人安裝了我們的套件後,通常來說就需要將我們的 Service Provider 列在其中。不過,其實不需要讓使用者手動將 Service Provider 寫在列表中,我們可以在套件的 composer.json 檔中的 extra 欄位內定義 Provider。除了 Servce Provider 內,也可以列出要註冊的 Facade

1"extra": {
2 "laravel": {
3 "providers": [
4 "Barryvdh\\Debugbar\\ServiceProvider"
5 ],
6 "aliases": {
7 "Debugbar": "Barryvdh\\Debugbar\\Facade"
8 }
9 }
10},
1"extra": {
2 "laravel": {
3 "providers": [
4 "Barryvdh\\Debugbar\\ServiceProvider"
5 ],
6 "aliases": {
7 "Debugbar": "Barryvdh\\Debugbar\\Facade"
8 }
9 }
10},

設定好 Discovery 後,Larave 就會在套件安裝時自動註冊套件的 Service Provider 與 Facade,帶給套件使用者一個方便的體驗。

不使用 Package Discovery

若有使用到某個套件且想為該套件禁用 Package Discovery 的話,可以將該套件名稱列在專案 composer.json 檔中的 extra 段落內:

1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "barryvdh/laravel-debugbar"
5 ]
6 }
7},
1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "barryvdh/laravel-debugbar"
5 ]
6 }
7},

可以在 dont-discover 指示詞內使用 * 字元來禁用所有套件的 Package Discovery:

1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "*"
5 ]
6 }
7},
1"extra": {
2 "laravel": {
3 "dont-discover": [
4 "*"
5 ]
6 }
7},

Service Provider

Service Provider 是套件與 Laravel 間的連結點。Service Provider 負責將各種東西繫結到 Laravel 的 Service Container 上,並告訴 Laravel 要在哪裡載入套件的資源,如 View、設定檔、語系檔等。

Service Provider 應繼承 Illuminate\Support\ServiceProvider 類別,並包含兩個方法:registerbootServiceProvider 基礎類別放在 illuminate/support Composer 套件中,請在你的套件中將其加為相依性套件。若要瞭解更多有關 Service Provider 的架構與功能,請參見 Service Provider 的說明文件

資源

設定

一般來說,在製作套件的時候我們會想將套件的設定檔安裝到專案的 config 目錄內。這樣一來套件使用者就能輕鬆地覆寫預設設定。若要讓設定檔能安裝到專案內,請在 Service Provider 的 boot 方法內呼叫 publishes 方法:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../config/courier.php' => config_path('courier.php'),
10 ]);
11}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../config/courier.php' => config_path('courier.php'),
10 ]);
11}

然後,當套件使用者執行 Laravel 的 vendor:publish 指令時,這些檔案就會被複製到指定的安裝(Publish)地點。安裝好設定檔後,就可以像其他設定檔樣存取這些設定值:

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

請不要在設定檔中定義閉包。因為當使用者執行 config:cache Artisan 指令時,這些閉包沒有辦法被序列化。

預設套件設定

可以將套件自己的設定檔跟安裝到專案裡的設定檔合併。這樣一來,就能讓使用者在設定檔中只定義要覆寫的值。若要合併設定檔,請在 Service Provider 的 register 方法中使用 mergeConfigFrom 方法。

mergeConfigFrom 方法接受套件設定檔的路徑作為其第一個引數,而專案中的設定檔名稱則為其第二個引數:

1/**
2 * Register any application services.
3 *
4 * @return void
5 */
6public function register()
7{
8 $this->mergeConfigFrom(
9 __DIR__.'/../config/courier.php', 'courier'
10 );
11}
1/**
2 * Register any application services.
3 *
4 * @return void
5 */
6public function register()
7{
8 $this->mergeConfigFrom(
9 __DIR__.'/../config/courier.php', 'courier'
10 );
11}
exclamation

該方法只會合併設定陣列中的第一層。若套件使用者只定義了多為陣列中的一部分,則未定義的部分將不會被合併。

Route

若套件中包含 Route,可使用 loadRoutesFrom 方法。該方法會自動判斷專案的 Route 是否有被快取,當有快取時將不會載入這些 Route 檔:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
9}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
9}

Migration

若套件中有 資料庫 Migration,可使用 loadMigrationsFrom 方法來讓 Laravel 載入這些 Migration。loadMigrationsFrom 方法接受套件 Migartion 的路徑作為其唯一的引數:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
9}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
9}

Migration 註冊好後,執行 php artisan migrate 指令時,就會自動執行這些 Migration。不需要將這些檔案匯出到 database/migrations 目錄中。

翻譯

若套件有包含語系檔,可使用 loadTranslationsFrom 方法來讓 Laravel 載入這些檔案。舉例來說,若套件名稱為 courier,則可在 Service Provider 的 boot 方法中這樣寫:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
9}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
9}

套件的語系檔使用 package::file.line (套件::檔名.行) 語法慣例來參照。所以,courier 套件的 messages 檔案中,welcome 行可以這樣載入:

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

安裝翻譯

若想將套件的語系檔安裝到專案的 lang/vendor 目錄下,可使用 Service Provider 的 publishes 方法。publishes 方法接受一組套件路徑與欲安裝位置的陣列。舉例來說,若要為 courier 套件安裝語系檔,可以這樣寫:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
9 
10 $this->publishes([
11 __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
12 ]);
13}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
9 
10 $this->publishes([
11 __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
12 ]);
13}

接著,當套件使用者執行 Laravel 的 vendor:publish Artisan 指令後,套件的翻譯語系檔就會被安裝到指定的安裝位置內。

View

若要向 Laravel 註冊套件的 [View],我們需要告訴 Laravel 這些 View 存在哪裡。可以使用 Service Provider 的 loadViewsFrom 方法。loadViewsFrom 方法接受兩個引數:View 樣板的路徑,以及套件的名稱。舉例來說,若套件名稱為 courier,則可在 Service Provider 的 boot 方法中加入下列程式:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
9}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
9}

套件的 View 使用 package::view (套件::View) 語法慣例來參照。所以,在 Service Provider 內註冊好 View 的路徑後,就可以在 courier 套件中像這樣載入 dashboard View:

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

覆寫套件的 View

使用 loadViewsFrom 方法時,Laravel 實際上在兩個地方上都註冊為這個套件的 View 存放位置:專案的 resources/views/vendor 目錄,以及你所指定的目錄。所以,若以 courier 套件為例,Laravel 會先檢查 resources/views/vendor/courier 目錄下是否有開發人員覆寫的自訂版本。若沒找到自訂版本,Laravel 接著才會在呼叫 loadViewsFrom 時提供的路徑下搜尋套件的 View。這樣一來,套件使用者就能輕鬆地客製化 / 覆寫套件的 View。

安裝 View

若想讓 View 可被安裝到專案的 resources/views/vendor 目錄下,可使用 Service Provider 的 publishes 方法。publishes 方法接受一組套件 View 路徑與欲安裝路徑的陣列:

1/**
2 * Bootstrap the package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
9 
10 $this->publishes([
11 __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
12 ]);
13}
1/**
2 * Bootstrap the package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
9 
10 $this->publishes([
11 __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
12 ]);
13}

接著,當套件使用者執行 Laravel 的 vendor:publish Artisan 指令後,套件的 View 就會被複製到指定的安裝位置內。

View 元件

若想製作使用 Blade 元件的套件或將元件放在不符合慣例的目錄內,則需要手動註冊元件類別與其 HTML 標籤別名,以讓 Laravel 知道要在哪裡尋找元件。通常,應在套件的 Service Provider 內的 boot 方法中註冊你的元件:

1use Illuminate\Support\Facades\Blade;
2use VendorPackage\View\Components\AlertComponent;
3 
4/**
5 * Bootstrap your package's services.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Blade::component('package-alert', AlertComponent::class);
12}
1use Illuminate\Support\Facades\Blade;
2use VendorPackage\View\Components\AlertComponent;
3 
4/**
5 * Bootstrap your package's services.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Blade::component('package-alert', AlertComponent::class);
12}

註冊好元件後,便可使用其標籤別名來轉譯:

1<x-package-alert/>
1<x-package-alert/>

自動載入套件元件

或者,也可以使用 componentNamespace 方法來依照慣例自動載入元件類別。舉例來說,Nightshade 套件可能包含了放在 Nightshade\Views\Components Namespace 下的 CalendarColorPicker 元件:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 *
6 * @return void
7 */
8public function boot()
9{
10 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
11}
1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 *
6 * @return void
7 */
8public function boot()
9{
10 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
11}

這樣一來,就可以讓套件元件通過其 Vendor Namespace 來使用 package-name:: 語法:

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

Blade 會通過將元件名稱轉為 Pascal 命名法來自動偵測與這個元件關連的類別。也可以使用「點」語法來支援子目錄。

匿名元件

若套件中有匿名元件,則這些套件必須放在套件「view」目錄 (即 loadViewsFrom 方法 所指定的目錄) 下的 components 目錄內。接著,就可以使用套件 View 命名空間作為前置詞來轉譯套件:

1<x-courier::alert />
1<x-courier::alert />

「About」Artisan 指令

Laravel 的內建 about Artisan 指令提供了有關專案環境與設定的一覽。套件也可以使用 AboutCommand 類別來將額外資訊推入該指令的輸出中。一般來說,可在套件 Service Provider 的 boot 方法內加上該資訊:

1use Illuminate\Foundation\Console\AboutCommand;
2 
3/**
4 * Bootstrap any application services.
5 *
6 * @return void
7 */
8public function boot()
9{
10 AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
11}
1use Illuminate\Foundation\Console\AboutCommand;
2 
3/**
4 * Bootstrap any application services.
5 *
6 * @return void
7 */
8public function boot()
9{
10 AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
11}

指令

若要向 Laravel 註冊 Artisan 指令,可使用 commands 方法。這個方法接受一組指令類別名稱的陣列。註冊好指令後,就可以使用 Artisan CLI 來執行這些指令:

1use Courier\Console\Commands\InstallCommand;
2use Courier\Console\Commands\NetworkCommand;
3 
4/**
5 * Bootstrap any package services.
6 *
7 * @return void
8 */
9public function boot()
10{
11 if ($this->app->runningInConsole()) {
12 $this->commands([
13 InstallCommand::class,
14 NetworkCommand::class,
15 ]);
16 }
17}
1use Courier\Console\Commands\InstallCommand;
2use Courier\Console\Commands\NetworkCommand;
3 
4/**
5 * Bootstrap any package services.
6 *
7 * @return void
8 */
9public function boot()
10{
11 if ($this->app->runningInConsole()) {
12 $this->commands([
13 InstallCommand::class,
14 NetworkCommand::class,
15 ]);
16 }
17}

公用素材

套件也可以有像 JavaScript、CSS、圖片等的素材。若要將這些素材安裝到 public 目錄內,可使用 Service Provider 的 publishes 方法。在這個範例中,我們也給這些素材嫁了一個 public 素材群組標籤,這樣我們就能使用該標籤來輕鬆將一組相關的素材安裝到專案內:

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../public' => public_path('vendor/courier'),
10 ], 'public');
11}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../public' => public_path('vendor/courier'),
10 ], 'public');
11}

接著,當專案使用者執行 vendor:publish 指令後,素材就會被複製到指定的位置。由於使用者通常會需要在每次套件更新後都覆寫這些素材,因此可以使用 --force 旗標:

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

安裝檔案群組

有時候我們可能會想分別安裝套件素材與資源。舉例來說,我們可以讓使用者安裝套件的設定檔,但不強制使用者安裝套件素材。我們可以通過在 Service Provider 內呼叫 publishes 方法時為這些檔案指定「標籤」。舉例來說,我們來在 Service Provider 內 boot 方法中為 courier 套件定義兩個安裝群組 (courier-configcourier-migrations):

1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../config/package.php' => config_path('package.php')
10 ], 'courier-config');
11 
12 $this->publishes([
13 __DIR__.'/../database/migrations/' => database_path('migrations')
14 ], 'courier-migrations');
15}
1/**
2 * Bootstrap any package services.
3 *
4 * @return void
5 */
6public function boot()
7{
8 $this->publishes([
9 __DIR__.'/../config/package.php' => config_path('package.php')
10 ], 'courier-config');
11 
12 $this->publishes([
13 __DIR__.'/../database/migrations/' => database_path('migrations')
14 ], 'courier-migrations');
15}

接著,使用者在執行 vendor:publish 指令時就可以使用標籤來分別安裝這些群組:

1php artisan vendor:publish --tag=courier-config
1php artisan vendor:publish --tag=courier-config
翻譯進度
100% 已翻譯
更新時間:
2023年2月11日 下午12:59:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.