> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 套件的 Blade 擴充與編譯快取

> 解讀 Laravel 13 的 BladeCompiler，說明自訂指令與條件式的註冊、編譯期與執行期的分離，以及套件更新時的 View 快取驗證。

要從套件提供 Blade 語法，應將**把模板轉換為 PHP 的處理**與**執行所產生 PHP 的處理**分開。若在編譯時嵌入目前的設定值，快取的 View 可能會持續顯示舊的值。

本頁參照 Laravel 官方文件 `13.x` 與 Framework `v13.37.0` 的實作，深入探討[套件開發基礎](/zh-TW/advanced/package-development)。一般的模板語法請參閱 [Blade](/zh-TW/blade)，View 的命名空間與公開請參閱[套件 View 的覆寫與更新](/zh-TW/advanced/package-views)。

## 區分註冊、編譯與執行

傳給 `Blade::directive()` 的回呼函式回傳的不是要顯示的值，而是**要產生的 PHP 程式碼字串**。參數 `$expression` 也不是已求值的值，而是寫在模板中的 PHP 運算式。

```mermaid theme={null}
flowchart TD
    A["在提供者的 boot 中註冊擴充"] --> B["渲染 View"]
    B --> C{"需要編譯？"}
    C -->|Yes| D["以 directive 的回呼函式<br>產生 PHP 程式碼"]
    D --> E["儲存編譯結果"]
    C -->|No| F["使用已儲存的 PHP"]
    E --> G["執行 PHP 產生 HTML"]
    F --> G
    G --> H["參照執行時的設定與參數"]
```

| API | 所註冊回呼函式的角色 | 執行的時機 |
| - | - | - |
| `Blade::directive()` | 產生 PHP 程式碼字串 | 編譯目標指令時 |
| `Blade::if()` | 判斷條件 | 已產生的 PHP 呼叫 `Blade::check()` 時 |

`view:cache` 也會進行編譯。在指令的產生用回呼函式中，應設計成不取得目前使用者、不寫入資料庫、也不呼叫外部 API。

## 以套件專屬名稱註冊

以下範例提供 `@acmeCourierLabel($code)`，在配送代碼前顯示所設定的標籤。前提是透過 Composer 的 PSR-4 設定將 `Acme\Courier\` 對應到 `src/`，並以[自動探索](/zh-TW/advanced/package-discovery)或手動方式註冊提供者。

```php src/CourierServiceProvider.php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Blade::directive('acmeCourierLabel', function (string $expression): string {
            return "<?php echo e((string) config('courier.label', 'Tracking').': '.({$expression})); ?>";
        });

        Blade::if('acmeCourierEnabled', function (): bool {
            return (bool) config('courier.enabled', false);
        });
    }
}
```

此範例的設定契約為：`courier.label` 是字串，`courier.enabled` 是 boolean。設定檔的發布與合併請參閱[套件設定的合併與快取](/zh-TW/advanced/package-config-merging)。

註冊在一般提供者的 `boot()` 中進行，不要以 `runningInConsole()` 條件包住。HTTP 渲染與 Artisan 的 `view:cache` 都需要相同的擴充。也請避免將只註冊 Blade 擴充的提供者設為延遲提供者，使其直到其他服務被解析時才啟動。

### 參數以運算式而非值的形式接收

```blade theme={null}
@acmeCourierLabel($code)
```

在此模板中，回呼函式接收到的是字串 `$code`。產生的 PHP 如下所示。

```php theme={null}
<?php echo e((string) config('courier.label', 'Tracking').': '.($code)); ?>
```

`config()` 與 `$code` 的求值發生在執行此 PHP 的時間點。這與回呼函式本身讀取 `config()` 並回傳固定字串的實作不同。

經確認，`callCustomDirective()` 會移除外側的括號並 trim 運算式後，再傳給回呼函式。它不會將參數轉換為 PHP 的值，也不會拆分成個別參數。此範例以**傳入一個必要運算式的語法**公開。

處理多個參數時，請不要單純以逗號進行 `explode()`。陣列、方法呼叫與字串常值中也可能含有逗號。若要處理屬性或複雜的 HTML，以 [Blade 元件](/zh-TW/blade)的形式提供，較容易劃分職責。

<Warning>
  模板中的運算式是由可信任的開發者撰寫的 PHP。請不要以使用者輸入組合模板的原始碼。此外，自訂指令的 `echo` 並不會自動套用一般 `{{ }}` 的跳脫處理。在此範例中，所產生的 PHP 會明確呼叫 `e()`。
</Warning>

### 名稱衝突也是相容性問題

`directive()` 會以名稱為鍵，將處理器儲存到陣列中。若名稱相同就會覆寫先前的註冊，而 `compileStatement()` 會優先選擇自訂指令，而非內建的轉換方法。官方測試中也有以自訂註冊覆寫 `foreach` 的案例。

請避免 `label` 或 `enabled` 這類通用名稱與內建名稱，並加上套件專屬的前綴。使用者在模板中使用的名稱與參數應視為公開 API，變更時請提供遷移步驟。

## 條件分支以 Blade::if 在執行時判斷

上方提供者所註冊的條件可以如下使用。

```blade theme={null}
@acmeCourierEnabled
    @acmeCourierLabel($code)
@else
    配送追蹤已停用。
@endacmeCourierEnabled
```

`Blade::if()` 會先儲存條件回呼函式，再註冊開始、`unless`、`else` 與結束用的指令。此範例的開始部分會轉換為以下 PHP。

```php theme={null}
<?php if (\Illuminate\Support\Facades\Blade::check('acmeCourierEnabled')): ?>
```

檢查設定的條件回呼函式，會在執行時由 `check()` 呼叫。這並不是在編譯時固定 true 或 false 結果的處理。即使是使用已編譯 View 的 HTTP 程序，也需要註冊條件的回呼函式。

此語法是用來切換顯示內容，而非配送操作的授權。存取控制也要在路由或操作端實施。

## 套件更新不一定會自動重新產生 View

經確認，`Compiler::isExpired()` 會檢查快取是否存在，並依設定比對原始 View 與已編譯檔案的更新時間。**它不會將提供者中所寫指令的變更作為相依檔案來追蹤。**

| 變更內容 | 對已編譯 PHP 的影響 |
| - | - |
| 變更 `directive()` 回傳的程式碼 | 舊的 PHP 會殘留，因此需要重新編譯 |
| 只變更 `Blade::if()` 條件回呼函式的處理 | 可從已產生的 `check()` 呼叫新的處理，但需要載入新程式碼 |
| 變更指令名稱或參數的契約 | 需要修改使用者的模板並重新編譯 |
| View 已公開到應用程式端 | 僅更新套件內的 View 不會更新已公開的副本 |

官方文件也建議在變更指令的邏輯後，以 `view:clear` 刪除快取。請在使用端應用程式的部署流程中，於更新後的套件與使用者的 View 一致之後執行。

```bash theme={null}
php artisan view:clear
php artisan view:cache
```

`view:cache` 是在正式環境中預先編譯時執行。請不要從套件的 `boot()` 每次都清除快取，也不要在安裝時擅自改寫使用端應用程式的部署流程。設定快取的更新與常駐程序的重新載入，也請依照使用環境的一般部署步驟進行。

## 分別驗證編譯結果與渲染結果

首先，在已註冊上述提供者的測試用 Laravel 應用程式中，驗證所產生的 PHP。以下是要加入繼承 `Tests\TestCase` 的 Feature 測試中的方法。

```php theme={null}
public function test_label_directive_compiles_to_runtime_lookup(): void
{
    $compiled = \Illuminate\Support\Facades\Blade::compileString(
        '@acmeCourierLabel($code)',
    );

    $this->assertSame(
        "<?php echo e((string) config('courier.label', 'Tracking').': '.(\$code)); ?>",
        $compiled,
    );
}
```

此測試可以確認產生 PHP 的契約，但無法驗證執行時的跳脫處理與條件分支。實際的套件還要加入 View 渲染與更新時的驗證。

| 確認案例 | 預期結果 |
| - | - |
| 在配送代碼中傳入會被解讀為 HTML 的字元 | 透過所產生的 `e()` 進行跳脫 |
| 使用相同的已編譯 View，並在渲染前變更標籤設定 | 下一次渲染會顯示新的設定值 |
| 將 `courier.enabled` 設為 true 或 false 後渲染 | 只顯示對應的分支 |
| 在 `view:cache` 後啟動新的應用程式 | 擴充已註冊，結果與未預先編譯時相同 |
| 變更指令的產生程式碼並部署 | 重建快取後使用新的 PHP |
| 更新支援的 Laravel 版本 | 產生程式碼、渲染、使用快取的各項測試皆通過 |

## 相關頁面

<Columns cols={2}>
  <Card title="套件 View 的覆寫與更新" icon="eye" href="/zh-TW/advanced/package-views">
    確認命名空間的搜尋順序，以及維護已公開模板的方法。
  </Card>

  <Card title="套件快取與 optimize 的整合" icon="gears" href="/zh-TW/advanced/package-optimization">
    確認將自訂快取的建立與刪除整合到部署流程的方法。
  </Card>
</Columns>

## 參考的一手資料

* [Laravel 官方文件：Extending Blade・Custom if statements](https://github.com/laravel/docs/blob/12080c72699b99ec6573f7e79ff4834d9001b6bc/blade.md#extending-blade)
* [Laravel 官方文件：套件的 View 與元件](https://github.com/laravel/docs/blob/12080c72699b99ec6573f7e79ff4834d9001b6bc/packages.md#views)
* [BladeCompiler：directive()・callCustomDirective()・compileStatement()・if()・check()](https://github.com/laravel/framework/blob/v13.37.0/src/Illuminate/View/Compilers/BladeCompiler.php)
* [Compiler：以 isExpired() 判斷快取](https://github.com/laravel/framework/blob/v13.37.0/src/Illuminate/View/Compilers/Compiler.php)
* [BladeCustomTest：自訂指令、條件與內建語法的覆寫](https://github.com/laravel/framework/blob/v13.37.0/tests/View/Blade/BladeCustomTest.php)


## Related topics

- [進階主題](/zh-TW/advanced/index.md)
- [套件 View 的覆寫與更新](/zh-TW/advanced/package-views.md)
- [套件的路由註冊與快取](/zh-TW/advanced/package-routes.md)
- [快取](/zh-TW/cache.md)
- [套件的事件監聽器註冊與快取](/zh-TW/advanced/package-events.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.