> ## 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を読み解き、独自ディレクティブと条件式の登録、コンパイル時と実行時の分離、パッケージ更新時のビューキャッシュ検証を解説します。

パッケージからBladeの記法を提供するには、**テンプレートをPHPへ変換する処理**と、**生成したPHPを実行する処理**を分けます。現在の設定値をコンパイル時に埋め込むと、キャッシュされたビューで古い値を表示し続けることがあります。

このページではLaravel公式ドキュメントの `13.x` とFramework `v13.37.0` の実装を参照し、[パッケージ開発の基礎](/jp/advanced/package-development)を掘り下げます。通常のテンプレート記法は[Blade](/jp/blade)、ビューの名前空間と公開は[パッケージビューの上書きと更新](/jp/advanced/package-views)を参照してください。

## 登録・コンパイル・実行を区別する

`Blade::directive()` に渡すコールバックは、表示する値ではなく**生成するPHPコードの文字列**を返します。引数の `$expression` も、評価済みの値ではなくテンプレートに書かれたPHP式です。

```mermaid theme={null}
flowchart TD
    A["プロバイダーのbootで拡張を登録"] --> B["ビューを描画する"]
    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` でもコンパイルが行われます。ディレクティブの生成用コールバックでは、現在のユーザーの取得、DBへの書き込み、外部API呼び出しなどを行わない設計にします。

## パッケージ固有の名前で登録する

例として、配送コードの前に設定されたラベルを表示する `@acmeCourierLabel($code)` を提供します。ComposerのPSR-4設定で `Acme\Courier\` を `src/` に対応させ、プロバイダーを[自動検出](/jp/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という設定の契約にします。設定ファイルの配布やマージは[パッケージ設定のマージとキャッシュ](/jp/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の値に変換したり、個別の引数へ分割したりはしません。この例は**必須の式を1つ渡す記法**として公開します。

複数引数を扱う場合、単純にカンマで `explode()` しないでください。配列、メソッド呼び出し、文字列リテラルの中にもカンマが含まれます。属性や複雑なHTMLを扱いたい場合は、[Bladeコンポーネント](/jp/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を固定する処理ではありません。コンパイル済みビューを使うHTTPプロセスでも、条件のコールバック登録が必要です。

この記法は表示の切り替えであり、配送操作の認可ではありません。アクセス制御はルートや操作側でも実施します。

## パッケージ更新でビューが自動再生成されるとは限らない

確認した `Compiler::isExpired()` は、キャッシュの有無と、設定に応じて元ビューとコンパイル済みファイルの更新時刻を調べます。**プロバイダーに書かれたディレクティブの変更を依存ファイルとして追跡する処理ではありません。**

| 変更内容 | コンパイル済みPHPへの影響 |
| - | - |
| `directive()` が返すコードを変更 | 古いPHPが残るため再コンパイルが必要 |
| `Blade::if()` の条件コールバックの処理だけを変更 | 生成済みの `check()` から新しい処理を呼べるが、新コードの読み込みが必要 |
| ディレクティブ名や引数の契約を変更 | 利用者のテンプレート修正と再コンパイルが必要 |
| ビューをアプリ側へ公開済み | パッケージ内のビュー更新だけでは公開済みコピーは更新されない |

公式ドキュメントも、ディレクティブのロジックを変更した後は `view:clear` でキャッシュを削除するよう案内しています。利用アプリケーションのデプロイ工程で、更新したパッケージと利用者のビューをそろえた後に実行します。

```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を生成する契約を確認できますが、実行時のエスケープや条件分岐までは検証できません。実際のパッケージには、ビュー描画と更新時の検証も追加します。

| 確認ケース | 期待する結果 |
| - | - |
| 配送コードにHTMLとして解釈される文字を渡す | 生成した `e()` によりエスケープされる |
| 同じコンパイル済みビューを使い、描画前にラベル設定を変える | 次の描画は新しい設定値を表示する |
| `courier.enabled` をtrue・falseにして描画 | 対応する分岐だけが表示される |
| `view:cache` 後に新しいアプリケーションを起動 | 拡張が登録され、事前コンパイルなしの場合と同じ結果になる |
| ディレクティブの生成コードを変更してデプロイ | キャッシュ再作成後に新しいPHPが使われる |
| Laravelの対応バージョンを更新 | 生成コード・描画・キャッシュ利用の各テストが通る |

## 関連ページ

<Columns cols={2}>
  <Card title="パッケージビューの上書きと更新" icon="eye" href="/jp/advanced/package-views">
    名前空間の検索順序と、公開済みテンプレートを保守する方法を確認します。
  </Card>

  <Card title="パッケージのキャッシュとoptimizeへの統合" icon="gears" href="/jp/advanced/package-optimization">
    独自キャッシュの構築・削除をデプロイ工程へ統合する方法を確認します。
  </Card>
</Columns>

## 参照した一次情報

* [Laravel公式ドキュメント: Extending Blade・Custom if statements](https://github.com/laravel/docs/blob/12080c72699b99ec6573f7e79ff4834d9001b6bc/blade.md#extending-blade)
* [Laravel公式ドキュメント: パッケージのビューとコンポーネント](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

- [応用トピック](/jp/advanced/index.md)
- [パッケージビューの上書きと更新](/jp/advanced/package-views.md)
- [パッケージのルート登録とキャッシュ](/jp/advanced/package-routes.md)
- [Laravelパッケージ開発](/jp/advanced/package-development.md)
- [パッケージのキャッシュとoptimizeへの統合](/jp/advanced/package-optimization.md)


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