Skip to content

套件開發

簡介

套件是為 Laravel 增加功能的主要方式。套件可以是任何東西,像是處理日期的好工具 Carbon,或是讓你能將檔案與 Eloquent 模型關聯的套件,例如 Spatie 的 Laravel Media Library

套件有不同的類型。有些套件是獨立的,這意味著它們可以與任何 PHP 框架搭配使用。Carbon 和 Pest 就是獨立套件的例子。任何這類套件都可以透過在你的 composer.json 檔案中要求 (Require) 它們來與 Laravel 搭配使用。

另一方面,其他套件則是專門為 Laravel 設計的。這些套件可能包含專門用於增強 Laravel 應用程式的路由、控制器、視圖和設定。本指南主要涵蓋開發那些 Laravel 專用套件。

關於 Facades 的注意事項

在撰寫 Laravel 應用程式時,通常使用 Contracts 或 Facades 並無大礙,因為兩者基本上都提供同等程度的可測試性。然而,在撰寫套件時,你的套件通常無法存取 Laravel 的所有測試輔助工具。如果你希望能夠像在典型的 Laravel 應用程式中安裝套件一樣編寫套件測試,你可以使用 Orchestral Testbench 套件。

套件探索

Laravel 應用程式的 bootstrap/providers.php 檔案包含了應由 Laravel 載入的服務提供者列表。但是,你可以在套件的 composer.json 檔案中的 extra 區段定義該提供者,讓 Laravel 自動載入,而不必手動將服務提供者加入列表。除了服務提供者外,你還可以列出任何你想要註冊的 facades

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

一旦你的套件配置了探索功能,Laravel 會在安裝時自動註冊其服務提供者與 Facades,為你的套件使用者提供便利的安裝體驗。

選擇退出套件探索

如果你是套件的使用者,並希望對某個套件停用套件探索,你可以在應用程式的 composer.json 檔案中的 extra 區段列出該套件名稱:

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

你可以使用應用程式 dont-discover 指令中的 * 字元來停用所有套件的套件探索:

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

服務提供者

服務提供者是你的套件與 Laravel 之間的連接點。服務提供者負責將事物綁定到 Laravel 的服務容器中,並通知 Laravel 從何處載入套件資源,例如視圖、設定和語言檔。

服務提供者繼承了 Illuminate\Support\ServiceProvider 類別,並包含兩個方法:registerboot。基礎的 ServiceProvider 類別位於 illuminate/support Composer 套件中,你應該將其加入到你自己的套件依賴中。要進一步瞭解服務提供者的結構與目的,請參閱其文件

資源

設定

通常,您需要將套件的設定檔發佈到應用程式的 config 目錄。這將允許您的套件使用者輕鬆覆蓋您的預設設定選項。要允許發佈設定檔,請從服務提供者的 boot 方法中呼叫 publishes 方法:

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

現在,當您的套件使用者執行 Laravel 的 vendor:publish 指令時,您的檔案將被複製到指定的發佈位置。一旦您的設定被發佈,就可以像存取其他設定檔一樣存取它的值:

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

⚠️ 警告

您不應該在設定檔中定義閉包。當使用者執行 config:cache Artisan 指令時,它們無法被正確序列化。

預設套件設定

您也可以將自己的套件設定檔與應用程式已發佈的副本合併。這將允許您的使用者僅在已發佈的設定檔副本中定義他們實際想要覆蓋的選項。要合併設定檔的值,請在服務提供者的 register 方法中使用 mergeConfigFrom 方法。

mergeConfigFrom 方法接受套件設定檔的路徑作為其第一個參數,並接受應用程式設定檔副本的名稱作為其第二個參數:

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

⚠️ 警告

此方法僅合併設定陣列的第一層。如果您的使用者部分定義了一個多維設定陣列,則缺失的選項將不會被合併。

路由

如果您的套件包含路由,您可以使用 loadRoutesFrom 方法載入它們。此方法將自動判斷應用程式的路由是否已被快取,如果路由已經被快取,則不會載入您的路由檔案:

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

資料庫遷移

如果您的套件包含資料庫遷移,您可以使用 publishesMigrations 方法來通知 Laravel 指定的目錄或檔案包含遷移。當 Laravel 發佈遷移時,它會自動更新檔案名稱中的時間戳記,以反映當前的日期和時間:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}

語言檔

如果您的套件包含語言檔,您可以使用 loadTranslationsFrom 方法來通知 Laravel 如何載入它們。例如,如果您的套件名稱為 courier,您應該在服務提供者的 boot 方法中加入以下內容:

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

套件翻譯行是使用 package::file.line 語法慣例來引用的。因此,您可以像這樣從 messages 檔案中載入 courier 套件的 welcome 行:

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

您可以使用 loadJsonTranslationsFrom 方法為您的套件註冊 JSON 翻譯檔案。此方法接受包含套件 JSON 翻譯檔案的目錄路徑:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}

發佈語言檔

如果您想將套件的語言檔發佈到應用程式的 lang/vendor 目錄,可以使用服務提供者的 publishes 方法。publishes 方法接受一個包含套件路徑及其所需發佈位置的陣列。例如,要發佈 courier 套件的語言檔,您可以執行以下操作:

php
/**
 * Bootstrap any package services.
 */
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 方法中加入以下內容:

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

套件視圖是使用 package::view 語法慣例來引用的。因此,一旦您的視圖路徑在服務提供者中註冊,您就可以像這樣從 courier 套件中載入 dashboard 視圖:

php
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 方法。publishes 方法接受一個包含套件視圖路徑及其所需發佈位置的陣列:

php
/**
 * Bootstrap the package services.
 */
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 方法中註冊您的組件:

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

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}

一旦您的組件被註冊,就可以使用其標籤別名來進行渲染:

blade
<x-package-alert/>

自動載入套件組件

或者,您可以使用 componentNamespace 方法來依據慣例自動載入組件類別。例如,一個 Nightshade 套件可能擁有位於 Nightshade\Views\Components 命名空間下的 CalendarColorPicker 組件:

php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

這將允許使用 package-name:: 語法透過其開發者命名空間來使用套件組件:

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

Blade 會自動透過將組件名稱轉換為 PascalCase 來偵測連結到該組件的類別。也支援使用「點 (dot)」記號來表示子目錄。

匿名組件

如果您的套件包含匿名組件,它們必須被放置在套件「視圖」目錄(如 loadViewsFrom 方法 所指定)的 components 目錄中。接著,您可以透過在組件名稱前加上套件的視圖命名空間前綴來渲染它們:

blade
<x-courier::alert />

「About」Artisan 指令

Laravel 內建的 about Artisan 指令提供了應用程式環境與設定的摘要。套件可以透過 AboutCommand 類別將額外資訊推送到此指令的輸出。通常,這些資訊可以從您的套件服務提供者的 boot 方法中加入:

php
use Illuminate\Foundation\Console\AboutCommand;

/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}

指令

若要向 Laravel 註冊您的套件 Artisan 指令,您可以使用 commands 方法。此方法預期接收一個指令類別名稱的陣列。一旦指令註冊完成,您就可以使用 Artisan CLI 來執行它們:

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

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

最佳化指令

Laravel 的 最佳化指令 會快取應用程式的設定、事件、路由和視圖。透過使用 optimizes 方法,您可以註冊自己套件的 Artisan 指令,使其在執行 optimizeoptimize:clear 指令時被呼叫:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'package:optimize',
            clear: 'package:clear-optimizations',
        );
    }
}

重新載入指令

Laravel 的 重新載入指令 會終止所有執行中的服務,以便系統程序監控器能自動重新啟動它們。透過使用 reloads 方法,您可以註冊自己套件的 Artisan 指令,使其在執行 reload 指令時被呼叫:

php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->reloads('package:reload');
    }
}

公開資產

您的套件可能包含 JavaScript、CSS 和圖片等資產。若要將這些資產發佈到應用程式的 public 目錄,請使用服務提供者的 publishes 方法。在下方的範例中,我們還會加入一個 public 資產群組標籤,這可以用來輕鬆發佈相關的資產群組:

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

現在,當您的套件使用者執行 vendor:publish 指令時,您的資產將被複製到指定的發佈位置。由於使用者通常需要在每次套件更新時覆寫資產,因此您可以使用 --force 旗標:

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

發佈檔案群組

您可能希望分開發佈套件的資產與資源群組。例如,您可能希望允許使用者僅發佈套件的設定檔,而不必強迫發佈套件的資產。您可以透過在套件服務提供者的 publishes 方法中對其進行「標籤化 (Tagging)」來達成。例如,讓我們在套件服務提供者的 boot 方法中,使用標籤為 courier 套件定義兩個發佈群組 (courier-configcourier-migrations):

php
/**
 * Bootstrap any package services.
 */
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 指令時引用其標籤,來分開發佈這些群組:

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

您的使用者也可以使用 --provider 旗標來發佈由您套件服務提供者定義的所有可發佈檔案:

shell
php artisan vendor:publish --provider="Your\Package\ServiceProvider"